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. Custom Fields and Validation Create custom serializer fields for complex data types. Nested Serializers Handle complex nested relationships. ViewSets Create RESTful endpoints with ViewSets. Routers Configure URL routing for ViewSets. Permissions Implement authentication and authorization. Authentication Configure various authentication methods. Filtering and Search Impl…

,\n mapping={'get': 'list', 'post': 'create'},\n name='{basename}-list',\n detail=False,\n initkwargs={}\n ),\n # Add custom routes\n ]\n```\n\n## Permissions\n\nImplement authentication and authorization.\n\n```python\nfrom rest_framework import permissions\n\nclass IsAuthorOrReadOnly(permissions.BasePermission):\n \"\"\"Custom permission to only allow authors to edit.\"\"\"\n\n def has_object_permission(self, request, view, obj):\n # Read permissions for any request\n if request.method in permissions.SAFE_METHODS:\n return True\n\n # Write permissions only for author\n return obj.author == request.user\n\nclass IsOwnerOrAdmin(permissions.BasePermission):\n def has_object_permission(self, request, view, obj):\n return obj.owner == request.user or request.user.is_staff\n\n# Usage in ViewSet\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n permission_classes = [permissions.IsAuthenticated, IsAuthorOrReadOnly]\n\n# Multiple permission classes\nfrom rest_framework.permissions import IsAuthenticated, IsAdminUser\n\nclass AdminPostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_permissions(self):\n if self.action in ['create', 'update', 'partial_update', 'destroy']:\n return [IsAdminUser()]\n return [IsAuthenticated()]\n```\n\n## Authentication\n\nConfigure various authentication methods.\n\n```python\nfrom rest_framework.authentication import TokenAuthentication, SessionAuthentication\nfrom rest_framework.authtoken.models import Token\nfrom rest_framework.permissions import IsAuthenticated\n\n# Token Authentication\nclass PostViewSet(viewsets.ModelViewSet):\n authentication_classes = [TokenAuthentication]\n permission_classes = [IsAuthenticated]\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n# Create token for user\nfrom rest_framework.authtoken.views import obtain_auth_token\nfrom django.urls import path\n\nurlpatterns = [\n path('api-token-auth/', obtain_auth_token),\n]\n\n# Custom token authentication\nfrom rest_framework.authtoken.views import ObtainAuthToken\nfrom rest_framework.authtoken.models import Token\nfrom rest_framework.response import Response\n\nclass CustomAuthToken(ObtainAuthToken):\n def post(self, request, *args, **kwargs):\n serializer = self.serializer_class(data=request.data,\n context={'request': request})\n serializer.is_valid(raise_exception=True)\n user = serializer.validated_data['user']\n token, created = Token.objects.get_or_create(user=user)\n return Response({\n 'token': token.key,\n 'user_id': user.pk,\n 'email': user.email\n })\n\n# JWT Authentication (using djangorestframework-simplejwt)\nfrom rest_framework_simplejwt.authentication import JWTAuthentication\n\nclass PostViewSet(viewsets.ModelViewSet):\n authentication_classes = [JWTAuthentication]\n permission_classes = [IsAuthenticated]\n```\n\n## Filtering and Search\n\nImplement advanced filtering capabilities.\n\n```python\nfrom django_filters import rest_framework as filters\nfrom rest_framework import filters as drf_filters\n\nclass PostFilter(filters.FilterSet):\n title = filters.CharFilter(lookup_expr='icontains')\n created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')\n created_before = filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')\n\n class Meta:\n model = Post\n fields = ['author', 'published', 'title']\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n filter_backends = [\n filters.DjangoFilterBackend,\n drf_filters.SearchFilter,\n drf_filters.OrderingFilter\n ]\n filterset_class = PostFilter\n search_fields = ['title', 'content', 'author__name']\n ordering_fields = ['created_at', 'title', 'views']\n ordering = ['-created_at']\n\n# Custom filter backend\nclass IsOwnerFilterBackend(filters.BaseFilterBackend):\n def filter_queryset(self, request, queryset, view):\n return queryset.filter(owner=request.user)\n```\n\n## Pagination\n\nConfigure pagination for large datasets.\n\n```python\nfrom rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination\n\nclass StandardResultsSetPagination(PageNumberPagination):\n page_size = 10\n page_size_query_param = 'page_size'\n max_page_size = 100\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n pagination_class = StandardResultsSetPagination\n\n# Cursor pagination for better performance\nclass PostCursorPagination(CursorPagination):\n page_size = 20\n ordering = '-created_at'\n\n# Custom pagination\nclass CustomPagination(PageNumberPagination):\n def get_paginated_response(self, data):\n return Response({\n 'links': {\n 'next': self.get_next_link(),\n 'previous': self.get_previous_link()\n },\n 'count': self.page.paginator.count,\n 'total_pages': self.page.paginator.num_pages,\n 'results': data\n })\n```\n\n## Throttling\n\nRate limit API requests.\n\n```python\nfrom rest_framework.throttling import UserRateThrottle, AnonRateThrottle\n\nclass BurstRateThrottle(UserRateThrottle):\n rate = '60/min'\n\nclass SustainedRateThrottle(UserRateThrottle):\n rate = '1000/day'\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n throttle_classes = [BurstRateThrottle, SustainedRateThrottle]\n\n# Custom throttle\nfrom rest_framework.throttling import SimpleRateThrottle\n\nclass UploadRateThrottle(SimpleRateThrottle):\n rate = '10/hour'\n\n def get_cache_key(self, request, view):\n if request.user.is_authenticated:\n ident = request.user.pk\n else:\n ident = self.get_ident(request)\n return self.cache_format % {'scope': self.scope, 'ident': ident}\n```\n\n## Versioning\n\nHandle API versioning.\n\n```python\nfrom rest_framework.versioning import URLPathVersioning, NamespaceVersioning\n\n# URL path versioning\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n versioning_class = URLPathVersioning\n\n def get_serializer_class(self):\n if self.request.version == 'v1':\n return PostSerializerV1\n return PostSerializerV2\n\n# URLs\nurlpatterns = [\n path('v1/posts/', PostViewSet.as_view({'get': 'list'})),\n path('v2/posts/', PostViewSet.as_view({'get': 'list'})),\n]\n\n# Accept header versioning\nfrom rest_framework.versioning import AcceptHeaderVersioning\n\nREST_FRAMEWORK = {\n 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',\n 'DEFAULT_VERSION': 'v1',\n 'ALLOWED_VERSIONS': ['v1', 'v2'],\n}\n```\n\n## Error Handling\n\nImplement custom error responses.\n\n```python\nfrom rest_framework.views import exception_handler\nfrom rest_framework.response import Response\n\ndef custom_exception_handler(exc, context):\n response = exception_handler(exc, context)\n\n if response is not None:\n response.data = {\n 'error': {\n 'status_code': response.status_code,\n 'message': response.data,\n 'detail': str(exc)\n }\n }\n\n return response\n\n# settings.py\nREST_FRAMEWORK = {\n 'EXCEPTION_HANDLER': 'myapp.utils.custom_exception_handler'\n}\n\n# Custom exceptions\nfrom rest_framework.exceptions import APIException\n\nclass ServiceUnavailable(APIException):\n status_code = 503\n default_detail = 'Service temporarily unavailable'\n default_code = 'service_unavailable'\n\n# Usage\nfrom rest_framework import status\nfrom rest_framework.response import Response\n\nclass PostViewSet(viewsets.ModelViewSet):\n def create(self, request):\n try:\n # Logic\n pass\n except Exception as e:\n raise ServiceUnavailable(detail=str(e))\n```\n\n## Advanced Serializer Patterns\n\nMaster complex serialization scenarios.\n\n```python\nfrom rest_framework import serializers\n\n# Dynamic field selection\nclass DynamicFieldsModelSerializer(serializers.ModelSerializer):\n \"\"\"Serializer that accepts 'fields' parameter to dynamically include/exclude fields.\"\"\"\n\n def __init__(self, *args, **kwargs):\n fields = kwargs.pop('fields', None)\n exclude = kwargs.pop('exclude', None)\n\n super().__init__(*args, **kwargs)\n\n if fields is not None:\n allowed = set(fields)\n existing = set(self.fields)\n for field_name in existing - allowed:\n self.fields.pop(field_name)\n\n if exclude is not None:\n for field_name in exclude:\n self.fields.pop(field_name, None)\n\nclass PostSerializer(DynamicFieldsModelSerializer):\n class Meta:\n model = Post\n fields = '__all__'\n\n# Usage:\nserializer = PostSerializer(post, fields=('id', 'title', 'author'))\nserializer = PostSerializer(post, exclude=('content',))\n\n# Serializer method field with context\nclass PostSerializer(serializers.ModelSerializer):\n is_liked = serializers.SerializerMethodField()\n like_count = serializers.SerializerMethodField()\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'is_liked', 'like_count']\n\n def get_is_liked(self, obj):\n request = self.context.get('request')\n if request and request.user.is_authenticated:\n return obj.likes.filter(user=request.user).exists()\n return False\n\n def get_like_count(self, obj):\n return obj.likes.count()\n\n# Nested writable serializers\nclass CommentSerializer(serializers.ModelSerializer):\n author_name = serializers.CharField(source='author.name', read_only=True)\n\n class Meta:\n model = Comment\n fields = ['id', 'content', 'author', 'author_name']\n\nclass PostSerializer(serializers.ModelSerializer):\n comments = CommentSerializer(many=True, required=False)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'content', 'comments']\n\n def create(self, validated_data):\n comments_data = validated_data.pop('comments', [])\n post = Post.objects.create(**validated_data)\n\n for comment_data in comments_data:\n Comment.objects.create(post=post, **comment_data)\n\n return post\n\n def update(self, instance, validated_data):\n comments_data = validated_data.pop('comments', None)\n\n instance.title = validated_data.get('title', instance.title)\n instance.content = validated_data.get('content', instance.content)\n instance.save()\n\n if comments_data is not None:\n # Clear existing comments\n instance.comments.all().delete()\n\n # Create new comments\n for comment_data in comments_data:\n Comment.objects.create(post=instance, **comment_data)\n\n return instance\n\n# Polymorphic serialization\nclass ContentSerializer(serializers.Serializer):\n \"\"\"Base serializer for polymorphic content.\"\"\"\n\n def to_representation(self, instance):\n if isinstance(instance, Article):\n return ArticleSerializer(instance, context=self.context).data\n elif isinstance(instance, Video):\n return VideoSerializer(instance, context=self.context).data\n elif isinstance(instance, Image):\n return ImageSerializer(instance, context=self.context).data\n return super().to_representation(instance)\n```\n\n## ViewSet Composition and Actions\n\nBuild sophisticated ViewSets with custom actions.\n\n```python\nfrom rest_framework import viewsets, status\nfrom rest_framework.decorators import action\nfrom rest_framework.response import Response\nfrom django.db.models import Count, Q\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_queryset(self):\n queryset = super().get_queryset()\n\n # Filter by query parameters\n author = self.request.query_params.get('author')\n if author:\n queryset = queryset.filter(author_id=author)\n\n published = self.request.query_params.get('published')\n if published is not None:\n queryset = queryset.filter(published=published == 'true')\n\n # Optimize based on action\n if self.action == 'list':\n queryset = queryset.select_related('author').only(\n 'id', 'title', 'created_at', 'author__name'\n )\n elif self.action == 'retrieve':\n queryset = queryset.select_related('author').prefetch_related(\n 'comments__author', 'tags'\n )\n\n return queryset\n\n def get_serializer_class(self):\n if self.action == 'list':\n return PostListSerializer\n elif self.action in ['create', 'update', 'partial_update']:\n return PostWriteSerializer\n return PostSerializer\n\n @action(detail=True, methods=['post'])\n def publish(self, request, pk=None):\n \"\"\"Publish a post.\"\"\"\n post = self.get_object()\n post.published = True\n post.published_at = timezone.now()\n post.save()\n\n serializer = self.get_serializer(post)\n return Response(serializer.data)\n\n @action(detail=True, methods=['post'])\n def like(self, request, pk=None):\n \"\"\"Like a post.\"\"\"\n post = self.get_object()\n user = request.user\n\n like, created = Like.objects.get_or_create(post=post, user=user)\n\n if not created:\n like.delete()\n return Response({'status': 'unliked'})\n\n return Response({'status': 'liked'}, status=status.HTTP_201_CREATED)\n\n @action(detail=False, methods=['get'])\n def trending(self, request):\n \"\"\"Get trending posts.\"\"\"\n posts = self.get_queryset().annotate(\n like_count=Count('likes')\n ).filter(\n created_at__gte=timezone.now() - timedelta(days=7)\n ).order_by('-like_count')[:10]\n\n serializer = self.get_serializer(posts, many=True)\n return Response(serializer.data)\n\n @action(detail=False, methods=['get'])\n def stats(self, request):\n \"\"\"Get post statistics.\"\"\"\n queryset = self.get_queryset()\n\n stats = {\n 'total': queryset.count(),\n 'published': queryset.filter(published=True).count(),\n 'drafts': queryset.filter(published=False).count(),\n 'total_likes': Like.objects.filter(post__in=queryset).count()\n }\n\n return Response(stats)\n\n @action(detail=True, methods=['get'])\n def comments(self, request, pk=None):\n \"\"\"Get comments for a post.\"\"\"\n post = self.get_object()\n comments = post.comments.select_related('author').all()\n\n page = self.paginate_queryset(comments)\n if page is not None:\n serializer = CommentSerializer(page, many=True)\n return self.get_paginated_response(serializer.data)\n\n serializer = CommentSerializer(comments, many=True)\n return Response(serializer.data)\n\n def perform_create(self, serializer):\n \"\"\"Save with current user as author.\"\"\"\n serializer.save(author=self.request.user)\n\n def perform_destroy(self, instance):\n \"\"\"Soft delete instead of hard delete.\"\"\"\n instance.deleted_at = timezone.now()\n instance.save()\n```\n\n## Advanced Permission Patterns\n\nImplement granular permission control.\n\n```python\nfrom rest_framework import permissions\n\nclass IsAuthorOrReadOnly(permissions.BasePermission):\n \"\"\"Object-level permission to only allow authors to edit.\"\"\"\n\n def has_object_permission(self, request, view, obj):\n if request.method in permissions.SAFE_METHODS:\n return True\n\n return obj.author == request.user\n\nclass IsPublishedOrAuthor(permissions.BasePermission):\n \"\"\"Only show published posts unless user is the author.\"\"\"\n\n def has_object_permission(self, request, view, obj):\n if obj.published:\n return True\n\n return obj.author == request.user\n\nclass HasAPIKey(permissions.BasePermission):\n \"\"\"Check for valid API key in header.\"\"\"\n\n def has_permission(self, request, view):\n api_key = request.META.get('HTTP_X_API_KEY')\n if not api_key:\n return False\n\n return APIKey.objects.filter(\n key=api_key,\n is_active=True\n ).exists()\n\nclass RateLimitPermission(permissions.BasePermission):\n \"\"\"Custom rate limiting based on user tier.\"\"\"\n\n def has_permission(self, request, view):\n user = request.user\n\n if not user.is_authenticated:\n return False\n\n # Check rate limit based on user tier\n if user.tier == 'premium':\n rate = 1000 # requests per day\n else:\n rate = 100\n\n # Implement rate limiting logic\n cache_key = f'rate_limit_{user.id}'\n current_count = cache.get(cache_key, 0)\n\n if current_count >= rate:\n return False\n\n cache.set(cache_key, current_count + 1, timeout=86400)\n return True\n\n# Combine multiple permissions\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_permissions(self):\n if self.action in ['create', 'update', 'partial_update', 'destroy']:\n permission_classes = [permissions.IsAuthenticated, IsAuthorOrReadOnly]\n elif self.action == 'list':\n permission_classes = [permissions.AllowAny]\n else:\n permission_classes = [IsPublishedOrAuthor]\n\n return [permission() for permission in permission_classes]\n```\n\n## Advanced Filtering and Search\n\nImplement sophisticated filtering capabilities.\n\n```python\nfrom django_filters import rest_framework as filters\nfrom rest_framework import filters as drf_filters\n\nclass PostFilter(filters.FilterSet):\n # Text filters\n title = filters.CharFilter(lookup_expr='icontains')\n title_exact = filters.CharFilter(field_name='title', lookup_expr='exact')\n\n # Date range filters\n created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')\n created_before = filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')\n\n # Number range filters\n min_views = filters.NumberFilter(field_name='views', lookup_expr='gte')\n max_views = filters.NumberFilter(field_name='views', lookup_expr='lte')\n\n # Choice filter\n status = filters.ChoiceFilter(choices=(\n ('published', 'Published'),\n ('draft', 'Draft'),\n ('archived', 'Archived')\n ))\n\n # Multiple choice filter\n tags = filters.ModelMultipleChoiceFilter(\n queryset=Tag.objects.all(),\n field_name='tags',\n conjoined=False # OR instead of AND\n )\n\n # Custom method filter\n has_comments = filters.BooleanFilter(method='filter_has_comments')\n\n class Meta:\n model = Post\n fields = ['author', 'published', 'category']\n\n def filter_has_comments(self, queryset, name, value):\n if value:\n return queryset.filter(comments__isnull=False).distinct()\n return queryset.filter(comments__isnull=True)\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n filter_backends = [\n filters.DjangoFilterBackend,\n drf_filters.SearchFilter,\n drf_filters.OrderingFilter\n ]\n filterset_class = PostFilter\n\n # Search configuration\n search_fields = [\n 'title',\n 'content',\n 'author__name',\n '=author__username', # Exact match\n '@description', # Full-text search (PostgreSQL)\n ]\n\n # Ordering configuration\n ordering_fields = ['created_at', 'updated_at', 'views', 'title']\n ordering = ['-created_at']\n\n# Custom filter backend\nclass IsOwnerFilterBackend(filters.BaseFilterBackend):\n \"\"\"Filter objects to show only user's own objects.\"\"\"\n\n def filter_queryset(self, request, queryset, view):\n if not request.user.is_authenticated:\n return queryset.none()\n\n return queryset.filter(author=request.user)\n\nclass MyPostViewSet(viewsets.ReadOnlyModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n filter_backends = [IsOwnerFilterBackend]\n```\n\n## Pagination Strategies\n\nImplement various pagination approaches.\n\n```python\nfrom rest_framework.pagination import (\n PageNumberPagination,\n LimitOffsetPagination,\n CursorPagination\n)\n\nclass StandardPagination(PageNumberPagination):\n page_size = 20\n page_size_query_param = 'page_size'\n max_page_size = 100\n\n def get_paginated_response(self, data):\n return Response({\n 'links': {\n 'next': self.get_next_link(),\n 'previous': self.get_previous_link()\n },\n 'count': self.page.paginator.count,\n 'total_pages': self.page.paginator.num_pages,\n 'current_page': self.page.number,\n 'results': data\n })\n\nclass LargeResultsPagination(PageNumberPagination):\n page_size = 1000\n max_page_size = 10000\n\nclass SmallResultsPagination(PageNumberPagination):\n page_size = 10\n\nclass PostCursorPagination(CursorPagination):\n page_size = 20\n ordering = '-created_at'\n cursor_query_param = 'cursor'\n\n def get_paginated_response(self, data):\n return Response({\n 'next': self.get_next_link(),\n 'previous': self.get_previous_link(),\n 'results': data\n })\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_pagination_class(self):\n if self.action == 'list':\n return StandardPagination\n elif self.action == 'trending':\n return SmallResultsPagination\n return None\n\n pagination_class = StandardPagination\n```\n\n## API Versioning Strategies\n\nManage API versions effectively.\n\n```python\nfrom rest_framework.versioning import (\n URLPathVersioning,\n NamespaceVersioning,\n AcceptHeaderVersioning,\n QueryParameterVersioning\n)\n\n# URL path versioning\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n versioning_class = URLPathVersioning\n\n def get_serializer_class(self):\n if self.request.version == 'v1':\n return PostSerializerV1\n elif self.request.version == 'v2':\n return PostSerializerV2\n return PostSerializer\n\n# URLs configuration\nurlpatterns = [\n path('v1/posts/', PostViewSet.as_view({'get': 'list'}), name='post-list-v1'),\n path('v2/posts/', PostViewSet.as_view({'get': 'list'}), name='post-list-v2'),\n]\n\n# Accept header versioning\nREST_FRAMEWORK = {\n 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',\n 'DEFAULT_VERSION': 'v1',\n 'ALLOWED_VERSIONS': ['v1', 'v2', 'v3'],\n 'VERSION_PARAM': 'version',\n}\n\n# Version-specific serializers\nclass PostSerializerV1(serializers.ModelSerializer):\n class Meta:\n model = Post\n fields = ['id', 'title', 'content'] # Minimal fields\n\nclass PostSerializerV2(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'content', 'author', 'created_at']\n\nclass PostSerializerV3(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n comments = CommentSerializer(many=True, read_only=True)\n tags = TagSerializer(many=True, read_only=True)\n\n class Meta:\n model = Post\n fields = '__all__'\n```\n\n## Testing DRF APIs\n\nWrite comprehensive tests for your API.\n\n```python\nfrom rest_framework.test import APITestCase, APIClient, APIRequestFactory\nfrom rest_framework import status\nfrom django.contrib.auth.models import User\nfrom django.urls import reverse\n\nclass PostAPITestCase(APITestCase):\n def setUp(self):\n self.client = APIClient()\n self.user = User.objects.create_user('testuser', '[email protected]', 'testpass')\n self.client.force_authenticate(user=self.user)\n\n def test_create_post(self):\n data = {'title': 'Test Post', 'content': 'Test content'}\n response = self.client.post('/api/posts/', data)\n self.assertEqual(response.status_code, status.HTTP_201_CREATED)\n self.assertEqual(Post.objects.count(), 1)\n self.assertEqual(Post.objects.get().title, 'Test Post')\n\n def test_list_posts(self):\n Post.objects.create(title='Post 1', author=self.user)\n Post.objects.create(title='Post 2', author=self.user)\n response = self.client.get('/api/posts/')\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n self.assertEqual(len(response.data['results']), 2)\n\n def test_update_post(self):\n post = Post.objects.create(title='Old Title', author=self.user)\n data = {'title': 'New Title'}\n response = self.client.patch(f'/api/posts/{post.id}/', data)\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n post.refresh_from_db()\n self.assertEqual(post.title, 'New Title')\n\n def test_delete_post(self):\n post = Post.objects.create(title='Test', author=self.user)\n response = self.client.delete(f'/api/posts/{post.id}/')\n self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)\n self.assertEqual(Post.objects.count(), 0)\n\n def test_unauthenticated_access(self):\n self.client.force_authenticate(user=None)\n response = self.client.post('/api/posts/', {})\n self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)\n\n def test_permission_denied(self):\n other_user = User.objects.create_user('other', password='pass')\n post = Post.objects.create(title='Test', author=other_user)\n\n response = self.client.patch(f'/api/posts/{post.id}/', {'title': 'Hacked'})\n self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)\n\n def test_filtering(self):\n Post.objects.create(title='Python Post', author=self.user, published=True)\n Post.objects.create(title='Django Post', author=self.user, published=False)\n\n response = self.client.get('/api/posts/?published=true')\n self.assertEqual(len(response.data['results']), 1)\n self.assertEqual(response.data['results'][0]['title'], 'Python Post')\n\n def test_search(self):\n Post.objects.create(title='Python Tutorial', author=self.user)\n Post.objects.create(title='Django Guide', author=self.user)\n\n response = self.client.get('/api/posts/?search=Python')\n self.assertEqual(len(response.data['results']), 1)\n\n def test_ordering(self):\n post1 = Post.objects.create(title='A Post', author=self.user)\n post2 = Post.objects.create(title='Z Post', author=self.user)\n\n response = self.client.get('/api/posts/?ordering=title')\n self.assertEqual(response.data['results'][0]['title'], 'A Post')\n\n response = self.client.get('/api/posts/?ordering=-title')\n self.assertEqual(response.data['results'][0]['title'], 'Z Post')\n\n def test_pagination(self):\n for i in range(25):\n Post.objects.create(title=f'Post {i}', author=self.user)\n\n response = self.client.get('/api/posts/')\n self.assertEqual(len(response.data['results']), 20) # Default page size\n self.assertIsNotNone(response.data['next'])\n\n def test_custom_action(self):\n post = Post.objects.create(title='Test', author=self.user)\n response = self.client.post(f'/api/posts/{post.id}/publish/')\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n\n post.refresh_from_db()\n self.assertTrue(post.published)\n\n# Testing with APIRequestFactory\nclass PostViewSetTestCase(APITestCase):\n def setUp(self):\n self.factory = APIRequestFactory()\n self.user = User.objects.create_user('testuser', password='testpass')\n\n def test_list_action(self):\n request = self.factory.get('/api/posts/')\n request.user = self.user\n\n view = PostViewSet.as_view({'get': 'list'})\n response = view(request)\n\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n\n def test_create_action(self):\n data = {'title': 'Test', 'content': 'Content'}\n request = self.factory.post('/api/posts/', data)\n request.user = self.user\n\n view = PostViewSet.as_view({'post': 'create'})\n response = view(request)\n\n self.assertEqual(response.status_code, status.HTTP_201_CREATED)\n```\n\n## When to Use This Skill\n\nUse django-rest-framework when building modern, production-ready\napplications that require\nadvanced patterns, best practices, and optimal performance.\n\n## Performance Optimization\n\nOptimize DRF API performance for production.\n\n```python\nfrom django.utils.decorators import method_decorator\nfrom django.views.decorators.cache import cache_page\nfrom django.views.decorators.vary import vary_on_headers\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_queryset(self):\n queryset = super().get_queryset()\n\n # Optimize based on action\n if self.action == 'list':\n # Minimal fields for list view\n queryset = queryset.select_related('author').only(\n 'id', 'title', 'created_at', 'author__name'\n )\n elif self.action == 'retrieve':\n # Full data for detail view\n queryset = queryset.select_related(\n 'author', 'category'\n ).prefetch_related(\n 'comments__author',\n 'tags'\n )\n\n return queryset\n\n # Cache list view for 5 minutes\n @method_decorator(cache_page(60 * 5))\n @method_decorator(vary_on_headers('Authorization'))\n def list(self, request, *args, **kwargs):\n return super().list(request, *args, **kwargs)\n\n# Use only() and defer() in serializers\nclass PostListSerializer(serializers.ModelSerializer):\n author_name = serializers.CharField(source='author.name', read_only=True)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'author_name', 'created_at']\n\nclass PostDetailSerializer(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n comments = CommentSerializer(many=True, read_only=True)\n\n class Meta:\n model = Post\n fields = '__all__'\n\n# Batch requests\nfrom rest_framework.response import Response\nfrom rest_framework import status\n\nclass BatchCreateMixin:\n \"\"\"Allow batch creation of objects.\"\"\"\n\n def create(self, request, *args, **kwargs):\n many = isinstance(request.data, list)\n\n if not many:\n return super().create(request, *args, **kwargs)\n\n serializer = self.get_serializer(data=request.data, many=True)\n serializer.is_valid(raise_exception=True)\n self.perform_create(serializer)\n\n return Response(serializer.data, status=status.HTTP_201_CREATED)\n\nclass PostViewSet(BatchCreateMixin, viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n```\n\n## Documentation and Schema\n\nGenerate API documentation automatically.\n\n```python\nfrom rest_framework import serializers, viewsets\nfrom rest_framework.decorators import action\nfrom drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiExample\nfrom drf_spectacular.types import OpenApiTypes\n\nclass PostSerializer(serializers.ModelSerializer):\n \"\"\"Serializer for Post objects.\"\"\"\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'content', 'author', 'created_at']\n read_only_fields = ['id', 'created_at']\n\nclass PostViewSet(viewsets.ModelViewSet):\n \"\"\"\n ViewSet for managing posts.\n\n Provides CRUD operations for posts with additional\n custom actions for publishing and liking.\n \"\"\"\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n @extend_schema(\n summary=\"Publish a post\",\n description=\"Set the post's published status to true\",\n responses={200: PostSerializer}\n )\n @action(detail=True, methods=['post'])\n def publish(self, request, pk=None):\n post = self.get_object()\n post.published = True\n post.save()\n serializer = self.get_serializer(post)\n return Response(serializer.data)\n\n @extend_schema(\n parameters=[\n OpenApiParameter(\n name='author',\n type=OpenApiTypes.INT,\n location=OpenApiParameter.QUERY,\n description='Filter by author ID'\n ),\n OpenApiParameter(\n name='published',\n type=OpenApiTypes.BOOL,\n location=OpenApiParameter.QUERY,\n description='Filter by published status'\n )\n ]\n )\n def list(self, request, *args, **kwargs):\n \"\"\"List posts with optional filtering.\"\"\"\n return super().list(request, *args, **kwargs)\n\n# settings.py\nREST_FRAMEWORK = {\n 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',\n}\n\nSPECTACULAR_SETTINGS = {\n 'TITLE': 'My API',\n 'DESCRIPTION': 'API for managing posts and comments',\n 'VERSION': '1.0.0',\n 'SERVE_INCLUDE_SCHEMA': False,\n}\n\n# urls.py\nfrom drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView\n\nurlpatterns = [\n path('api/schema/', SpectacularAPIView.as_view(), name='schema'),\n path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),\n]\n```\n\n## DRF Best Practices\n\n1. **Use ModelSerializer** - Leverage ModelSerializer to reduce boilerplate\n code\n2. **Validate at serializer level** - Implement validation in serializers, not\n views\n3. **Use ViewSets for standard CRUD** - ViewSets reduce code duplication for\n standard operations\n4. **Optimize with select_related** - Always optimize queries in\n get_queryset()\n5. **Version your API** - Plan for versioning from the start\n6. **Use proper permissions** - Implement granular permissions at object level\n7. **Implement pagination** - Always paginate list endpoints\n8. **Add throttling** - Protect your API with rate limiting\n9. **Use filtering backends** - Enable search and filtering for better UX\n10. **Write comprehensive tests** - Test all endpoints and permission scenarios\n11. **Cache expensive operations** - Use cache decorators for list views\n12. **Separate read/write serializers** - Use different serializers for\n different actions\n13. **Document your API** - Use drf-spectacular or similar for auto-generated\n docs\n14. **Handle errors gracefully** - Provide clear error messages for API\n consumers\n15. **Use bulk operations** - Support batch creation/updates for better\n performance\n\n## DRF Common Pitfalls\n\n1. **Not optimizing queries** - N+1 problems in serializers accessing related\n objects\n2. **Overly complex serializers** - Too much logic in serializers instead of\n models\n3. **Missing validation** - Not validating data at both field and object level\n4. **Inconsistent API design** - Not following REST conventions\n5. **No pagination** - Returning unbounded lists causes performance issues\n6. **Weak authentication** - Not implementing proper token expiration or\n refresh\n7. **Missing permissions** - Not implementing object-level permissions\n8. **No API versioning** - Breaking changes affect existing clients\n9. **Poor error messages** - Generic errors that don't help API consumers\n10. **Inadequate testing** - Not testing permissions, edge cases, and error\n scenarios\n11. **Exposing sensitive data** - Returning password hashes or internal IDs\n12. **Not using read_only_fields** - Allowing modification of computed fields\n13. **Ignoring CORS** - Not configuring CORS for frontend applications\n14. **Missing rate limiting** - APIs vulnerable to abuse without throttling\n15. **Not handling file uploads** - Improper handling of multipart/form-data\n requests\n\n## Resources\n\n- [Django REST Framework Documentation](https://www.django-rest-framework.org/)\n- [DRF Serializers Guide](https://www.django-rest-framework.org/api-guide/serializers/)\n- [DRF ViewSets](https://www.django-rest-framework.org/api-guide/viewsets/)\n- [DRF Authentication](https://www.django-rest-framework.org/api-guide/authentication/)\n- [DRF Testing](https://www.django-rest-framework.org/api-guide/testing/)\n---","attachment_filenames":[],"attachments":[],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Django REST Framework","type":"text"}]},{"type":"paragraph","content":[{"text":"Master Django REST Framework for building robust, scalable RESTful APIs with proper serialization and authentication.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Serializers","type":"text"}]},{"type":"paragraph","content":[{"text":"Build type-safe data serialization with Django REST Framework serializers.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework import serializers\nfrom django.contrib.auth.models import User\n\nclass UserSerializer(serializers.ModelSerializer):\n post_count = serializers.IntegerField(read_only=True)\n full_name = serializers.SerializerMethodField()\n\n class Meta:\n model = User\n fields = ['id', 'email', 'name', 'post_count', 'full_name']\n read_only_fields = ['id', 'created_at']\n extra_kwargs = {\n 'email': {'required': True},\n 'password': {'write_only': True}\n }\n\n def get_full_name(self, obj):\n return f\"{obj.first_name} {obj.last_name}\"\n\nclass PostSerializer(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n author_id = serializers.IntegerField(write_only=True)\n\n class Meta:\n model = Post\n fields = '__all__'\n\n def validate_title(self, value):\n if len(value) \u003c 5:\n raise serializers.ValidationError('Title must be at least 5 characters')\n return value\n\n def validate(self, data):\n if data.get('published') and not data.get('content'):\n raise serializers.ValidationError('Published posts must have content')\n return data\n\n def create(self, validated_data):\n # Custom creation logic\n post = Post.objects.create(**validated_data)\n # Send notification, etc.\n return post","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Custom Fields and Validation","type":"text"}]},{"type":"paragraph","content":[{"text":"Create custom serializer fields for complex data types.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework import serializers\n\nclass Base64ImageField(serializers.ImageField):\n \"\"\"Handle base64 encoded images.\"\"\"\n\n def to_internal_value(self, data):\n import base64\n from django.core.files.base import ContentFile\n\n if isinstance(data, str) and data.startswith('data:image'):\n format, imgstr = data.split(';base64,')\n ext = format.split('/')[-1]\n data = ContentFile(base64.b64decode(imgstr), name=f'temp.{ext}')\n\n return super().to_internal_value(data)\n\nclass PostSerializer(serializers.ModelSerializer):\n image = Base64ImageField(required=False)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'image']\n\n# Custom validators\ndef validate_no_profanity(value):\n profanity_words = ['bad', 'worse']\n if any(word in value.lower() for word in profanity_words):\n raise serializers.ValidationError('Content contains profanity')\n return value\n\nclass CommentSerializer(serializers.ModelSerializer):\n content = serializers.CharField(validators=[validate_no_profanity])\n\n class Meta:\n model = Comment\n fields = ['id', 'content', 'created_at']","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Nested Serializers","type":"text"}]},{"type":"paragraph","content":[{"text":"Handle complex nested relationships.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"class CommentSerializer(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n\n class Meta:\n model = Comment\n fields = ['id', 'content', 'author', 'created_at']\n\nclass PostSerializer(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n comments = CommentSerializer(many=True, read_only=True)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'content', 'author', 'comments']\n\n# Writable nested serializers\nclass PostCreateSerializer(serializers.ModelSerializer):\n comments = CommentSerializer(many=True, required=False)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'content', 'comments']\n\n def create(self, validated_data):\n comments_data = validated_data.pop('comments', [])\n post = Post.objects.create(**validated_data)\n\n for comment_data in comments_data:\n Comment.objects.create(post=post, **comment_data)\n\n return post\n\n# Dynamic nested serialization\nclass PostSerializer(serializers.ModelSerializer):\n class Meta:\n model = Post\n fields = ['id', 'title', 'content']\n\n def __init__(self, *args, **kwargs):\n include_comments = kwargs.pop('include_comments', False)\n super().__init__(*args, **kwargs)\n\n if include_comments:\n self.fields['comments'] = CommentSerializer(many=True, read_only=True)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"ViewSets","type":"text"}]},{"type":"paragraph","content":[{"text":"Create RESTful endpoints with ViewSets.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework import viewsets, permissions, status\nfrom rest_framework.decorators import action\nfrom rest_framework.response import Response\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n permission_classes = [permissions.IsAuthenticatedOrReadOnly]\n filterset_fields = ['author', 'published']\n search_fields = ['title', 'content']\n ordering_fields = ['created_at', 'title']\n\n def get_queryset(self):\n queryset = super().get_queryset()\n if self.action == 'list':\n queryset = queryset.filter(published=True)\n return queryset.select_related('author').prefetch_related('comments')\n\n def get_serializer_class(self):\n if self.action == 'create':\n return PostCreateSerializer\n return PostSerializer\n\n def perform_create(self, serializer):\n serializer.save(author=self.request.user)\n\n @action(detail=True, methods=['post'])\n def publish(self, request, pk=None):\n post = self.get_object()\n post.published = True\n post.save()\n return Response({'status': 'published'})\n\n @action(detail=False, methods=['get'])\n def recent(self, request):\n recent_posts = self.get_queryset()[:10]\n serializer = self.get_serializer(recent_posts, many=True)\n return Response(serializer.data)\n\n# ReadOnly ViewSet\nclass CategoryViewSet(viewsets.ReadOnlyModelViewSet):\n queryset = Category.objects.all()\n serializer_class = CategorySerializer","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Routers","type":"text"}]},{"type":"paragraph","content":[{"text":"Configure URL routing for ViewSets.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.routers import DefaultRouter, SimpleRouter\nfrom django.urls import path, include\n\n# Default router (with API root view)\nrouter = DefaultRouter()\nrouter.register(r'posts', PostViewSet, basename='post')\nrouter.register(r'users', UserViewSet, basename='user')\nrouter.register(r'comments', CommentViewSet, basename='comment')\n\nurlpatterns = [\n path('api/', include(router.urls)),\n]\n\n# Simple router (no API root)\nsimple_router = SimpleRouter()\nsimple_router.register(r'posts', PostViewSet)\n\n# Custom routing\nfrom rest_framework.routers import Route, DynamicRoute\n\nclass CustomRouter(DefaultRouter):\n routes = [\n Route(\n url=r'^{prefix}/

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. Custom Fields and Validation Create custom serializer fields for complex data types. Nested Serializers Handle complex nested relationships. ViewSets Create RESTful endpoints with ViewSets. Routers Configure URL routing for ViewSets. Permissions Implement authentication and authorization. Authentication Configure various authentication methods. Filtering and Search Impl…

,\n mapping={'get': 'list', 'post': 'create'},\n name='{basename}-list',\n detail=False,\n initkwargs={}\n ),\n # Add custom routes\n ]","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Permissions","type":"text"}]},{"type":"paragraph","content":[{"text":"Implement authentication and authorization.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework import permissions\n\nclass IsAuthorOrReadOnly(permissions.BasePermission):\n \"\"\"Custom permission to only allow authors to edit.\"\"\"\n\n def has_object_permission(self, request, view, obj):\n # Read permissions for any request\n if request.method in permissions.SAFE_METHODS:\n return True\n\n # Write permissions only for author\n return obj.author == request.user\n\nclass IsOwnerOrAdmin(permissions.BasePermission):\n def has_object_permission(self, request, view, obj):\n return obj.owner == request.user or request.user.is_staff\n\n# Usage in ViewSet\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n permission_classes = [permissions.IsAuthenticated, IsAuthorOrReadOnly]\n\n# Multiple permission classes\nfrom rest_framework.permissions import IsAuthenticated, IsAdminUser\n\nclass AdminPostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_permissions(self):\n if self.action in ['create', 'update', 'partial_update', 'destroy']:\n return [IsAdminUser()]\n return [IsAuthenticated()]","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Authentication","type":"text"}]},{"type":"paragraph","content":[{"text":"Configure various authentication methods.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.authentication import TokenAuthentication, SessionAuthentication\nfrom rest_framework.authtoken.models import Token\nfrom rest_framework.permissions import IsAuthenticated\n\n# Token Authentication\nclass PostViewSet(viewsets.ModelViewSet):\n authentication_classes = [TokenAuthentication]\n permission_classes = [IsAuthenticated]\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n# Create token for user\nfrom rest_framework.authtoken.views import obtain_auth_token\nfrom django.urls import path\n\nurlpatterns = [\n path('api-token-auth/', obtain_auth_token),\n]\n\n# Custom token authentication\nfrom rest_framework.authtoken.views import ObtainAuthToken\nfrom rest_framework.authtoken.models import Token\nfrom rest_framework.response import Response\n\nclass CustomAuthToken(ObtainAuthToken):\n def post(self, request, *args, **kwargs):\n serializer = self.serializer_class(data=request.data,\n context={'request': request})\n serializer.is_valid(raise_exception=True)\n user = serializer.validated_data['user']\n token, created = Token.objects.get_or_create(user=user)\n return Response({\n 'token': token.key,\n 'user_id': user.pk,\n 'email': user.email\n })\n\n# JWT Authentication (using djangorestframework-simplejwt)\nfrom rest_framework_simplejwt.authentication import JWTAuthentication\n\nclass PostViewSet(viewsets.ModelViewSet):\n authentication_classes = [JWTAuthentication]\n permission_classes = [IsAuthenticated]","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Filtering and Search","type":"text"}]},{"type":"paragraph","content":[{"text":"Implement advanced filtering capabilities.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from django_filters import rest_framework as filters\nfrom rest_framework import filters as drf_filters\n\nclass PostFilter(filters.FilterSet):\n title = filters.CharFilter(lookup_expr='icontains')\n created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')\n created_before = filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')\n\n class Meta:\n model = Post\n fields = ['author', 'published', 'title']\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n filter_backends = [\n filters.DjangoFilterBackend,\n drf_filters.SearchFilter,\n drf_filters.OrderingFilter\n ]\n filterset_class = PostFilter\n search_fields = ['title', 'content', 'author__name']\n ordering_fields = ['created_at', 'title', 'views']\n ordering = ['-created_at']\n\n# Custom filter backend\nclass IsOwnerFilterBackend(filters.BaseFilterBackend):\n def filter_queryset(self, request, queryset, view):\n return queryset.filter(owner=request.user)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pagination","type":"text"}]},{"type":"paragraph","content":[{"text":"Configure pagination for large datasets.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination\n\nclass StandardResultsSetPagination(PageNumberPagination):\n page_size = 10\n page_size_query_param = 'page_size'\n max_page_size = 100\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n pagination_class = StandardResultsSetPagination\n\n# Cursor pagination for better performance\nclass PostCursorPagination(CursorPagination):\n page_size = 20\n ordering = '-created_at'\n\n# Custom pagination\nclass CustomPagination(PageNumberPagination):\n def get_paginated_response(self, data):\n return Response({\n 'links': {\n 'next': self.get_next_link(),\n 'previous': self.get_previous_link()\n },\n 'count': self.page.paginator.count,\n 'total_pages': self.page.paginator.num_pages,\n 'results': data\n })","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Throttling","type":"text"}]},{"type":"paragraph","content":[{"text":"Rate limit API requests.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.throttling import UserRateThrottle, AnonRateThrottle\n\nclass BurstRateThrottle(UserRateThrottle):\n rate = '60/min'\n\nclass SustainedRateThrottle(UserRateThrottle):\n rate = '1000/day'\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n throttle_classes = [BurstRateThrottle, SustainedRateThrottle]\n\n# Custom throttle\nfrom rest_framework.throttling import SimpleRateThrottle\n\nclass UploadRateThrottle(SimpleRateThrottle):\n rate = '10/hour'\n\n def get_cache_key(self, request, view):\n if request.user.is_authenticated:\n ident = request.user.pk\n else:\n ident = self.get_ident(request)\n return self.cache_format % {'scope': self.scope, 'ident': ident}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Versioning","type":"text"}]},{"type":"paragraph","content":[{"text":"Handle API versioning.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.versioning import URLPathVersioning, NamespaceVersioning\n\n# URL path versioning\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n versioning_class = URLPathVersioning\n\n def get_serializer_class(self):\n if self.request.version == 'v1':\n return PostSerializerV1\n return PostSerializerV2\n\n# URLs\nurlpatterns = [\n path('v1/posts/', PostViewSet.as_view({'get': 'list'})),\n path('v2/posts/', PostViewSet.as_view({'get': 'list'})),\n]\n\n# Accept header versioning\nfrom rest_framework.versioning import AcceptHeaderVersioning\n\nREST_FRAMEWORK = {\n 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',\n 'DEFAULT_VERSION': 'v1',\n 'ALLOWED_VERSIONS': ['v1', 'v2'],\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error Handling","type":"text"}]},{"type":"paragraph","content":[{"text":"Implement custom error responses.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.views import exception_handler\nfrom rest_framework.response import Response\n\ndef custom_exception_handler(exc, context):\n response = exception_handler(exc, context)\n\n if response is not None:\n response.data = {\n 'error': {\n 'status_code': response.status_code,\n 'message': response.data,\n 'detail': str(exc)\n }\n }\n\n return response\n\n# settings.py\nREST_FRAMEWORK = {\n 'EXCEPTION_HANDLER': 'myapp.utils.custom_exception_handler'\n}\n\n# Custom exceptions\nfrom rest_framework.exceptions import APIException\n\nclass ServiceUnavailable(APIException):\n status_code = 503\n default_detail = 'Service temporarily unavailable'\n default_code = 'service_unavailable'\n\n# Usage\nfrom rest_framework import status\nfrom rest_framework.response import Response\n\nclass PostViewSet(viewsets.ModelViewSet):\n def create(self, request):\n try:\n # Logic\n pass\n except Exception as e:\n raise ServiceUnavailable(detail=str(e))","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Advanced Serializer Patterns","type":"text"}]},{"type":"paragraph","content":[{"text":"Master complex serialization scenarios.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework import serializers\n\n# Dynamic field selection\nclass DynamicFieldsModelSerializer(serializers.ModelSerializer):\n \"\"\"Serializer that accepts 'fields' parameter to dynamically include/exclude fields.\"\"\"\n\n def __init__(self, *args, **kwargs):\n fields = kwargs.pop('fields', None)\n exclude = kwargs.pop('exclude', None)\n\n super().__init__(*args, **kwargs)\n\n if fields is not None:\n allowed = set(fields)\n existing = set(self.fields)\n for field_name in existing - allowed:\n self.fields.pop(field_name)\n\n if exclude is not None:\n for field_name in exclude:\n self.fields.pop(field_name, None)\n\nclass PostSerializer(DynamicFieldsModelSerializer):\n class Meta:\n model = Post\n fields = '__all__'\n\n# Usage:\nserializer = PostSerializer(post, fields=('id', 'title', 'author'))\nserializer = PostSerializer(post, exclude=('content',))\n\n# Serializer method field with context\nclass PostSerializer(serializers.ModelSerializer):\n is_liked = serializers.SerializerMethodField()\n like_count = serializers.SerializerMethodField()\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'is_liked', 'like_count']\n\n def get_is_liked(self, obj):\n request = self.context.get('request')\n if request and request.user.is_authenticated:\n return obj.likes.filter(user=request.user).exists()\n return False\n\n def get_like_count(self, obj):\n return obj.likes.count()\n\n# Nested writable serializers\nclass CommentSerializer(serializers.ModelSerializer):\n author_name = serializers.CharField(source='author.name', read_only=True)\n\n class Meta:\n model = Comment\n fields = ['id', 'content', 'author', 'author_name']\n\nclass PostSerializer(serializers.ModelSerializer):\n comments = CommentSerializer(many=True, required=False)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'content', 'comments']\n\n def create(self, validated_data):\n comments_data = validated_data.pop('comments', [])\n post = Post.objects.create(**validated_data)\n\n for comment_data in comments_data:\n Comment.objects.create(post=post, **comment_data)\n\n return post\n\n def update(self, instance, validated_data):\n comments_data = validated_data.pop('comments', None)\n\n instance.title = validated_data.get('title', instance.title)\n instance.content = validated_data.get('content', instance.content)\n instance.save()\n\n if comments_data is not None:\n # Clear existing comments\n instance.comments.all().delete()\n\n # Create new comments\n for comment_data in comments_data:\n Comment.objects.create(post=instance, **comment_data)\n\n return instance\n\n# Polymorphic serialization\nclass ContentSerializer(serializers.Serializer):\n \"\"\"Base serializer for polymorphic content.\"\"\"\n\n def to_representation(self, instance):\n if isinstance(instance, Article):\n return ArticleSerializer(instance, context=self.context).data\n elif isinstance(instance, Video):\n return VideoSerializer(instance, context=self.context).data\n elif isinstance(instance, Image):\n return ImageSerializer(instance, context=self.context).data\n return super().to_representation(instance)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"ViewSet Composition and Actions","type":"text"}]},{"type":"paragraph","content":[{"text":"Build sophisticated ViewSets with custom actions.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework import viewsets, status\nfrom rest_framework.decorators import action\nfrom rest_framework.response import Response\nfrom django.db.models import Count, Q\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_queryset(self):\n queryset = super().get_queryset()\n\n # Filter by query parameters\n author = self.request.query_params.get('author')\n if author:\n queryset = queryset.filter(author_id=author)\n\n published = self.request.query_params.get('published')\n if published is not None:\n queryset = queryset.filter(published=published == 'true')\n\n # Optimize based on action\n if self.action == 'list':\n queryset = queryset.select_related('author').only(\n 'id', 'title', 'created_at', 'author__name'\n )\n elif self.action == 'retrieve':\n queryset = queryset.select_related('author').prefetch_related(\n 'comments__author', 'tags'\n )\n\n return queryset\n\n def get_serializer_class(self):\n if self.action == 'list':\n return PostListSerializer\n elif self.action in ['create', 'update', 'partial_update']:\n return PostWriteSerializer\n return PostSerializer\n\n @action(detail=True, methods=['post'])\n def publish(self, request, pk=None):\n \"\"\"Publish a post.\"\"\"\n post = self.get_object()\n post.published = True\n post.published_at = timezone.now()\n post.save()\n\n serializer = self.get_serializer(post)\n return Response(serializer.data)\n\n @action(detail=True, methods=['post'])\n def like(self, request, pk=None):\n \"\"\"Like a post.\"\"\"\n post = self.get_object()\n user = request.user\n\n like, created = Like.objects.get_or_create(post=post, user=user)\n\n if not created:\n like.delete()\n return Response({'status': 'unliked'})\n\n return Response({'status': 'liked'}, status=status.HTTP_201_CREATED)\n\n @action(detail=False, methods=['get'])\n def trending(self, request):\n \"\"\"Get trending posts.\"\"\"\n posts = self.get_queryset().annotate(\n like_count=Count('likes')\n ).filter(\n created_at__gte=timezone.now() - timedelta(days=7)\n ).order_by('-like_count')[:10]\n\n serializer = self.get_serializer(posts, many=True)\n return Response(serializer.data)\n\n @action(detail=False, methods=['get'])\n def stats(self, request):\n \"\"\"Get post statistics.\"\"\"\n queryset = self.get_queryset()\n\n stats = {\n 'total': queryset.count(),\n 'published': queryset.filter(published=True).count(),\n 'drafts': queryset.filter(published=False).count(),\n 'total_likes': Like.objects.filter(post__in=queryset).count()\n }\n\n return Response(stats)\n\n @action(detail=True, methods=['get'])\n def comments(self, request, pk=None):\n \"\"\"Get comments for a post.\"\"\"\n post = self.get_object()\n comments = post.comments.select_related('author').all()\n\n page = self.paginate_queryset(comments)\n if page is not None:\n serializer = CommentSerializer(page, many=True)\n return self.get_paginated_response(serializer.data)\n\n serializer = CommentSerializer(comments, many=True)\n return Response(serializer.data)\n\n def perform_create(self, serializer):\n \"\"\"Save with current user as author.\"\"\"\n serializer.save(author=self.request.user)\n\n def perform_destroy(self, instance):\n \"\"\"Soft delete instead of hard delete.\"\"\"\n instance.deleted_at = timezone.now()\n instance.save()","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Advanced Permission Patterns","type":"text"}]},{"type":"paragraph","content":[{"text":"Implement granular permission control.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework import permissions\n\nclass IsAuthorOrReadOnly(permissions.BasePermission):\n \"\"\"Object-level permission to only allow authors to edit.\"\"\"\n\n def has_object_permission(self, request, view, obj):\n if request.method in permissions.SAFE_METHODS:\n return True\n\n return obj.author == request.user\n\nclass IsPublishedOrAuthor(permissions.BasePermission):\n \"\"\"Only show published posts unless user is the author.\"\"\"\n\n def has_object_permission(self, request, view, obj):\n if obj.published:\n return True\n\n return obj.author == request.user\n\nclass HasAPIKey(permissions.BasePermission):\n \"\"\"Check for valid API key in header.\"\"\"\n\n def has_permission(self, request, view):\n api_key = request.META.get('HTTP_X_API_KEY')\n if not api_key:\n return False\n\n return APIKey.objects.filter(\n key=api_key,\n is_active=True\n ).exists()\n\nclass RateLimitPermission(permissions.BasePermission):\n \"\"\"Custom rate limiting based on user tier.\"\"\"\n\n def has_permission(self, request, view):\n user = request.user\n\n if not user.is_authenticated:\n return False\n\n # Check rate limit based on user tier\n if user.tier == 'premium':\n rate = 1000 # requests per day\n else:\n rate = 100\n\n # Implement rate limiting logic\n cache_key = f'rate_limit_{user.id}'\n current_count = cache.get(cache_key, 0)\n\n if current_count >= rate:\n return False\n\n cache.set(cache_key, current_count + 1, timeout=86400)\n return True\n\n# Combine multiple permissions\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_permissions(self):\n if self.action in ['create', 'update', 'partial_update', 'destroy']:\n permission_classes = [permissions.IsAuthenticated, IsAuthorOrReadOnly]\n elif self.action == 'list':\n permission_classes = [permissions.AllowAny]\n else:\n permission_classes = [IsPublishedOrAuthor]\n\n return [permission() for permission in permission_classes]","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Advanced Filtering and Search","type":"text"}]},{"type":"paragraph","content":[{"text":"Implement sophisticated filtering capabilities.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from django_filters import rest_framework as filters\nfrom rest_framework import filters as drf_filters\n\nclass PostFilter(filters.FilterSet):\n # Text filters\n title = filters.CharFilter(lookup_expr='icontains')\n title_exact = filters.CharFilter(field_name='title', lookup_expr='exact')\n\n # Date range filters\n created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')\n created_before = filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')\n\n # Number range filters\n min_views = filters.NumberFilter(field_name='views', lookup_expr='gte')\n max_views = filters.NumberFilter(field_name='views', lookup_expr='lte')\n\n # Choice filter\n status = filters.ChoiceFilter(choices=(\n ('published', 'Published'),\n ('draft', 'Draft'),\n ('archived', 'Archived')\n ))\n\n # Multiple choice filter\n tags = filters.ModelMultipleChoiceFilter(\n queryset=Tag.objects.all(),\n field_name='tags',\n conjoined=False # OR instead of AND\n )\n\n # Custom method filter\n has_comments = filters.BooleanFilter(method='filter_has_comments')\n\n class Meta:\n model = Post\n fields = ['author', 'published', 'category']\n\n def filter_has_comments(self, queryset, name, value):\n if value:\n return queryset.filter(comments__isnull=False).distinct()\n return queryset.filter(comments__isnull=True)\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n filter_backends = [\n filters.DjangoFilterBackend,\n drf_filters.SearchFilter,\n drf_filters.OrderingFilter\n ]\n filterset_class = PostFilter\n\n # Search configuration\n search_fields = [\n 'title',\n 'content',\n 'author__name',\n '=author__username', # Exact match\n '@description', # Full-text search (PostgreSQL)\n ]\n\n # Ordering configuration\n ordering_fields = ['created_at', 'updated_at', 'views', 'title']\n ordering = ['-created_at']\n\n# Custom filter backend\nclass IsOwnerFilterBackend(filters.BaseFilterBackend):\n \"\"\"Filter objects to show only user's own objects.\"\"\"\n\n def filter_queryset(self, request, queryset, view):\n if not request.user.is_authenticated:\n return queryset.none()\n\n return queryset.filter(author=request.user)\n\nclass MyPostViewSet(viewsets.ReadOnlyModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n filter_backends = [IsOwnerFilterBackend]","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pagination Strategies","type":"text"}]},{"type":"paragraph","content":[{"text":"Implement various pagination approaches.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.pagination import (\n PageNumberPagination,\n LimitOffsetPagination,\n CursorPagination\n)\n\nclass StandardPagination(PageNumberPagination):\n page_size = 20\n page_size_query_param = 'page_size'\n max_page_size = 100\n\n def get_paginated_response(self, data):\n return Response({\n 'links': {\n 'next': self.get_next_link(),\n 'previous': self.get_previous_link()\n },\n 'count': self.page.paginator.count,\n 'total_pages': self.page.paginator.num_pages,\n 'current_page': self.page.number,\n 'results': data\n })\n\nclass LargeResultsPagination(PageNumberPagination):\n page_size = 1000\n max_page_size = 10000\n\nclass SmallResultsPagination(PageNumberPagination):\n page_size = 10\n\nclass PostCursorPagination(CursorPagination):\n page_size = 20\n ordering = '-created_at'\n cursor_query_param = 'cursor'\n\n def get_paginated_response(self, data):\n return Response({\n 'next': self.get_next_link(),\n 'previous': self.get_previous_link(),\n 'results': data\n })\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_pagination_class(self):\n if self.action == 'list':\n return StandardPagination\n elif self.action == 'trending':\n return SmallResultsPagination\n return None\n\n pagination_class = StandardPagination","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"API Versioning Strategies","type":"text"}]},{"type":"paragraph","content":[{"text":"Manage API versions effectively.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.versioning import (\n URLPathVersioning,\n NamespaceVersioning,\n AcceptHeaderVersioning,\n QueryParameterVersioning\n)\n\n# URL path versioning\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n versioning_class = URLPathVersioning\n\n def get_serializer_class(self):\n if self.request.version == 'v1':\n return PostSerializerV1\n elif self.request.version == 'v2':\n return PostSerializerV2\n return PostSerializer\n\n# URLs configuration\nurlpatterns = [\n path('v1/posts/', PostViewSet.as_view({'get': 'list'}), name='post-list-v1'),\n path('v2/posts/', PostViewSet.as_view({'get': 'list'}), name='post-list-v2'),\n]\n\n# Accept header versioning\nREST_FRAMEWORK = {\n 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',\n 'DEFAULT_VERSION': 'v1',\n 'ALLOWED_VERSIONS': ['v1', 'v2', 'v3'],\n 'VERSION_PARAM': 'version',\n}\n\n# Version-specific serializers\nclass PostSerializerV1(serializers.ModelSerializer):\n class Meta:\n model = Post\n fields = ['id', 'title', 'content'] # Minimal fields\n\nclass PostSerializerV2(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'content', 'author', 'created_at']\n\nclass PostSerializerV3(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n comments = CommentSerializer(many=True, read_only=True)\n tags = TagSerializer(many=True, read_only=True)\n\n class Meta:\n model = Post\n fields = '__all__'","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Testing DRF APIs","type":"text"}]},{"type":"paragraph","content":[{"text":"Write comprehensive tests for your API.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework.test import APITestCase, APIClient, APIRequestFactory\nfrom rest_framework import status\nfrom django.contrib.auth.models import User\nfrom django.urls import reverse\n\nclass PostAPITestCase(APITestCase):\n def setUp(self):\n self.client = APIClient()\n self.user = User.objects.create_user('testuser', '[email protected]', 'testpass')\n self.client.force_authenticate(user=self.user)\n\n def test_create_post(self):\n data = {'title': 'Test Post', 'content': 'Test content'}\n response = self.client.post('/api/posts/', data)\n self.assertEqual(response.status_code, status.HTTP_201_CREATED)\n self.assertEqual(Post.objects.count(), 1)\n self.assertEqual(Post.objects.get().title, 'Test Post')\n\n def test_list_posts(self):\n Post.objects.create(title='Post 1', author=self.user)\n Post.objects.create(title='Post 2', author=self.user)\n response = self.client.get('/api/posts/')\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n self.assertEqual(len(response.data['results']), 2)\n\n def test_update_post(self):\n post = Post.objects.create(title='Old Title', author=self.user)\n data = {'title': 'New Title'}\n response = self.client.patch(f'/api/posts/{post.id}/', data)\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n post.refresh_from_db()\n self.assertEqual(post.title, 'New Title')\n\n def test_delete_post(self):\n post = Post.objects.create(title='Test', author=self.user)\n response = self.client.delete(f'/api/posts/{post.id}/')\n self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)\n self.assertEqual(Post.objects.count(), 0)\n\n def test_unauthenticated_access(self):\n self.client.force_authenticate(user=None)\n response = self.client.post('/api/posts/', {})\n self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)\n\n def test_permission_denied(self):\n other_user = User.objects.create_user('other', password='pass')\n post = Post.objects.create(title='Test', author=other_user)\n\n response = self.client.patch(f'/api/posts/{post.id}/', {'title': 'Hacked'})\n self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)\n\n def test_filtering(self):\n Post.objects.create(title='Python Post', author=self.user, published=True)\n Post.objects.create(title='Django Post', author=self.user, published=False)\n\n response = self.client.get('/api/posts/?published=true')\n self.assertEqual(len(response.data['results']), 1)\n self.assertEqual(response.data['results'][0]['title'], 'Python Post')\n\n def test_search(self):\n Post.objects.create(title='Python Tutorial', author=self.user)\n Post.objects.create(title='Django Guide', author=self.user)\n\n response = self.client.get('/api/posts/?search=Python')\n self.assertEqual(len(response.data['results']), 1)\n\n def test_ordering(self):\n post1 = Post.objects.create(title='A Post', author=self.user)\n post2 = Post.objects.create(title='Z Post', author=self.user)\n\n response = self.client.get('/api/posts/?ordering=title')\n self.assertEqual(response.data['results'][0]['title'], 'A Post')\n\n response = self.client.get('/api/posts/?ordering=-title')\n self.assertEqual(response.data['results'][0]['title'], 'Z Post')\n\n def test_pagination(self):\n for i in range(25):\n Post.objects.create(title=f'Post {i}', author=self.user)\n\n response = self.client.get('/api/posts/')\n self.assertEqual(len(response.data['results']), 20) # Default page size\n self.assertIsNotNone(response.data['next'])\n\n def test_custom_action(self):\n post = Post.objects.create(title='Test', author=self.user)\n response = self.client.post(f'/api/posts/{post.id}/publish/')\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n\n post.refresh_from_db()\n self.assertTrue(post.published)\n\n# Testing with APIRequestFactory\nclass PostViewSetTestCase(APITestCase):\n def setUp(self):\n self.factory = APIRequestFactory()\n self.user = User.objects.create_user('testuser', password='testpass')\n\n def test_list_action(self):\n request = self.factory.get('/api/posts/')\n request.user = self.user\n\n view = PostViewSet.as_view({'get': 'list'})\n response = view(request)\n\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n\n def test_create_action(self):\n data = {'title': 'Test', 'content': 'Content'}\n request = self.factory.post('/api/posts/', data)\n request.user = self.user\n\n view = PostViewSet.as_view({'post': 'create'})\n response = view(request)\n\n self.assertEqual(response.status_code, status.HTTP_201_CREATED)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use This Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Use django-rest-framework when building modern, production-ready applications that require advanced patterns, best practices, and optimal performance.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Performance Optimization","type":"text"}]},{"type":"paragraph","content":[{"text":"Optimize DRF API performance for production.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from django.utils.decorators import method_decorator\nfrom django.views.decorators.cache import cache_page\nfrom django.views.decorators.vary import vary_on_headers\n\nclass PostViewSet(viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n def get_queryset(self):\n queryset = super().get_queryset()\n\n # Optimize based on action\n if self.action == 'list':\n # Minimal fields for list view\n queryset = queryset.select_related('author').only(\n 'id', 'title', 'created_at', 'author__name'\n )\n elif self.action == 'retrieve':\n # Full data for detail view\n queryset = queryset.select_related(\n 'author', 'category'\n ).prefetch_related(\n 'comments__author',\n 'tags'\n )\n\n return queryset\n\n # Cache list view for 5 minutes\n @method_decorator(cache_page(60 * 5))\n @method_decorator(vary_on_headers('Authorization'))\n def list(self, request, *args, **kwargs):\n return super().list(request, *args, **kwargs)\n\n# Use only() and defer() in serializers\nclass PostListSerializer(serializers.ModelSerializer):\n author_name = serializers.CharField(source='author.name', read_only=True)\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'author_name', 'created_at']\n\nclass PostDetailSerializer(serializers.ModelSerializer):\n author = UserSerializer(read_only=True)\n comments = CommentSerializer(many=True, read_only=True)\n\n class Meta:\n model = Post\n fields = '__all__'\n\n# Batch requests\nfrom rest_framework.response import Response\nfrom rest_framework import status\n\nclass BatchCreateMixin:\n \"\"\"Allow batch creation of objects.\"\"\"\n\n def create(self, request, *args, **kwargs):\n many = isinstance(request.data, list)\n\n if not many:\n return super().create(request, *args, **kwargs)\n\n serializer = self.get_serializer(data=request.data, many=True)\n serializer.is_valid(raise_exception=True)\n self.perform_create(serializer)\n\n return Response(serializer.data, status=status.HTTP_201_CREATED)\n\nclass PostViewSet(BatchCreateMixin, viewsets.ModelViewSet):\n queryset = Post.objects.all()\n serializer_class = PostSerializer","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Documentation and Schema","type":"text"}]},{"type":"paragraph","content":[{"text":"Generate API documentation automatically.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from rest_framework import serializers, viewsets\nfrom rest_framework.decorators import action\nfrom drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiExample\nfrom drf_spectacular.types import OpenApiTypes\n\nclass PostSerializer(serializers.ModelSerializer):\n \"\"\"Serializer for Post objects.\"\"\"\n\n class Meta:\n model = Post\n fields = ['id', 'title', 'content', 'author', 'created_at']\n read_only_fields = ['id', 'created_at']\n\nclass PostViewSet(viewsets.ModelViewSet):\n \"\"\"\n ViewSet for managing posts.\n\n Provides CRUD operations for posts with additional\n custom actions for publishing and liking.\n \"\"\"\n queryset = Post.objects.all()\n serializer_class = PostSerializer\n\n @extend_schema(\n summary=\"Publish a post\",\n description=\"Set the post's published status to true\",\n responses={200: PostSerializer}\n )\n @action(detail=True, methods=['post'])\n def publish(self, request, pk=None):\n post = self.get_object()\n post.published = True\n post.save()\n serializer = self.get_serializer(post)\n return Response(serializer.data)\n\n @extend_schema(\n parameters=[\n OpenApiParameter(\n name='author',\n type=OpenApiTypes.INT,\n location=OpenApiParameter.QUERY,\n description='Filter by author ID'\n ),\n OpenApiParameter(\n name='published',\n type=OpenApiTypes.BOOL,\n location=OpenApiParameter.QUERY,\n description='Filter by published status'\n )\n ]\n )\n def list(self, request, *args, **kwargs):\n \"\"\"List posts with optional filtering.\"\"\"\n return super().list(request, *args, **kwargs)\n\n# settings.py\nREST_FRAMEWORK = {\n 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',\n}\n\nSPECTACULAR_SETTINGS = {\n 'TITLE': 'My API',\n 'DESCRIPTION': 'API for managing posts and comments',\n 'VERSION': '1.0.0',\n 'SERVE_INCLUDE_SCHEMA': False,\n}\n\n# urls.py\nfrom drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView\n\nurlpatterns = [\n path('api/schema/', SpectacularAPIView.as_view(), name='schema'),\n path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),\n]","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"DRF Best Practices","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ModelSerializer","type":"text","marks":[{"type":"strong"}]},{"text":" - Leverage ModelSerializer to reduce boilerplate code","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validate at serializer level","type":"text","marks":[{"type":"strong"}]},{"text":" - Implement validation in serializers, not views","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ViewSets for standard CRUD","type":"text","marks":[{"type":"strong"}]},{"text":" - ViewSets reduce code duplication for standard operations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optimize with select_related","type":"text","marks":[{"type":"strong"}]},{"text":" - Always optimize queries in get_queryset()","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Version your API","type":"text","marks":[{"type":"strong"}]},{"text":" - Plan for versioning from the start","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use proper permissions","type":"text","marks":[{"type":"strong"}]},{"text":" - Implement granular permissions at object level","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement pagination","type":"text","marks":[{"type":"strong"}]},{"text":" - Always paginate list endpoints","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add throttling","type":"text","marks":[{"type":"strong"}]},{"text":" - Protect your API with rate limiting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use filtering backends","type":"text","marks":[{"type":"strong"}]},{"text":" - Enable search and filtering for better UX","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write comprehensive tests","type":"text","marks":[{"type":"strong"}]},{"text":" - Test all endpoints and permission scenarios","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cache expensive operations","type":"text","marks":[{"type":"strong"}]},{"text":" - Use cache decorators for list views","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Separate read/write serializers","type":"text","marks":[{"type":"strong"}]},{"text":" - Use different serializers for different actions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document your API","type":"text","marks":[{"type":"strong"}]},{"text":" - Use drf-spectacular or similar for auto-generated docs","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handle errors gracefully","type":"text","marks":[{"type":"strong"}]},{"text":" - Provide clear error messages for API consumers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use bulk operations","type":"text","marks":[{"type":"strong"}]},{"text":" - Support batch creation/updates for better performance","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"DRF Common Pitfalls","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not optimizing queries","type":"text","marks":[{"type":"strong"}]},{"text":" - N+1 problems in serializers accessing related objects","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Overly complex serializers","type":"text","marks":[{"type":"strong"}]},{"text":" - Too much logic in serializers instead of models","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing validation","type":"text","marks":[{"type":"strong"}]},{"text":" - Not validating data at both field and object level","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Inconsistent API design","type":"text","marks":[{"type":"strong"}]},{"text":" - Not following REST conventions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No pagination","type":"text","marks":[{"type":"strong"}]},{"text":" - Returning unbounded lists causes performance issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Weak authentication","type":"text","marks":[{"type":"strong"}]},{"text":" - Not implementing proper token expiration or refresh","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing permissions","type":"text","marks":[{"type":"strong"}]},{"text":" - Not implementing object-level permissions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No API versioning","type":"text","marks":[{"type":"strong"}]},{"text":" - Breaking changes affect existing clients","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Poor error messages","type":"text","marks":[{"type":"strong"}]},{"text":" - Generic errors that don't help API consumers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Inadequate testing","type":"text","marks":[{"type":"strong"}]},{"text":" - Not testing permissions, edge cases, and error scenarios","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Exposing sensitive data","type":"text","marks":[{"type":"strong"}]},{"text":" - Returning password hashes or internal IDs","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not using read_only_fields","type":"text","marks":[{"type":"strong"}]},{"text":" - Allowing modification of computed fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ignoring CORS","type":"text","marks":[{"type":"strong"}]},{"text":" - Not configuring CORS for frontend applications","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing rate limiting","type":"text","marks":[{"type":"strong"}]},{"text":" - APIs vulnerable to abuse without throttling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not handling file uploads","type":"text","marks":[{"type":"strong"}]},{"text":" - Improper handling of multipart/form-data requests","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Django REST Framework Documentation","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.django-rest-framework.org/","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DRF Serializers Guide","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.django-rest-framework.org/api-guide/serializers/","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DRF ViewSets","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.django-rest-framework.org/api-guide/viewsets/","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DRF Authentication","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.django-rest-framework.org/api-guide/authentication/","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DRF Testing","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.django-rest-framework.org/api-guide/testing/","title":null}}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"django-rest-framework","author":"@skillopedia","source":{"stars":163,"repo_name":"han","origin_url":"https://github.com/thebushidocollective/han/blob/HEAD/plugins/frameworks/django/skills/django-rest-framework/SKILL.md","repo_owner":"thebushidocollective","body_sha256":"2163cde07fdeb428200255689023047d55f38db4220d0d34ffc141e5721e6997","cluster_key":"aaf4a92eecf3e06627ae14453f228f16df14d3747a391e01fa2ba25da433795e","clean_bundle":{"format":"clean-skill-bundle-v1","source":"thebushidocollective/han/plugins/frameworks/django/skills/django-rest-framework/SKILL.md","bundle_sha256":"04d6f0c84f38ca790869bf4ca0965f7bc76aecc27ac76392b0484095cf3a4193","attachment_count":0,"text_attachments":0,"binary_attachments":0},"cluster_size":1,"skill_md_path":"plugins/frameworks/django/skills/django-rest-framework/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"web-development","import_tag":"clean-skills-v1","description":"Use when Django REST Framework for building APIs with serializers, viewsets, and authentication. Use when creating RESTful APIs.","allowed-tools":["Bash","Read"],"user-invocable":false}},"renderedAt":1782987056601}

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. Custom Fields and Validation Create custom serializer fields for complex data types. Nested Serializers Handle complex nested relationships. ViewSets Create RESTful endpoints with ViewSets. Routers Configure URL routing for ViewSets. Permissions Implement authentication and authorization. Authentication Configure various authentication methods. Filtering and Search Impl…