Django REST Framework
03 / 03

Routers, Pagination & ViewSets

Django REST Framework: Routers, Pagination & ViewSets

ViewSets & Routers

ViewSets combine the logic for a set of related views in a single class. Routers automatically generate URL patterns for ViewSets.

from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]

    def get_queryset(self):
        return Article.objects.filter(author=self.request.user)

    def get_serializer_class(self):
        if self.action == 'retrieve':
            return ArticleDetailSerializer
        return ArticleSerializer

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    # Custom action
    @action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
    def publish(self, request, pk=None):
        article = self.get_object()
        article.status = 'published'
        article.save()
        return Response({'status': 'published'})

    @action(detail=False, methods=['get'])
    def featured(self, request):
        featured = Article.objects.filter(is_featured=True)
        serializer = self.get_serializer(featured, many=True)
        return Response(serializer.data)

# urls.py
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('articles', ArticleViewSet, basename='article')
# Generates:
# GET/POST    /articles/
# GET/PUT/PATCH/DELETE /articles/<pk>/
# POST        /articles/<pk>/publish/
# GET         /articles/featured/

urlpatterns = [
    path('api/', include(router.urls)),
]

# ReadOnlyModelViewSet — only list() and retrieve()
class PublicArticleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Article.objects.filter(status='published')
    serializer_class = ArticleSerializer
    permission_classes = [AllowAny]

Pagination

# settings.py (global)
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
}

# Custom pagination classes
from rest_framework.pagination import PageNumberPagination, CursorPagination

class LargeResultsSetPagination(PageNumberPagination):
    page_size = 100
    page_size_query_param = 'page_size'  # ?page_size=50
    max_page_size = 1000

class ArticleCursorPagination(CursorPagination):
    page_size = 20
    ordering = '-created_at'   # must be unique, stable field

# Response format (PageNumberPagination):
# {
#   "count": 1023,
#   "next": "http://api.example.com/articles/?page=2",
#   "previous": null,
#   "results": [...]
# }

# Per-view pagination
class ArticleViewSet(viewsets.ModelViewSet):
    pagination_class = LargeResultsSetPagination

Throttling & Versioning

# Throttling (rate limiting)
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day',
    }
}

# Per-view throttle
class ArticleView(APIView):
    throttle_classes = [UserRateThrottle]

# URL versioning
REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION': 'v1',
    'ALLOWED_VERSIONS': ['v1', 'v2'],
    'VERSION_PARAM': 'version',
}

# urls.py
urlpatterns = [
    path('api/<version>/articles/', ArticleListView.as_view()),
]

# In view
class ArticleListView(APIView):
    def get(self, request, *args, **kwargs):
        if request.version == 'v2':
            serializer = ArticleV2Serializer(...)
        else:
            serializer = ArticleSerializer(...)

Best Practices

  • Use ModelViewSet + Router for standard CRUD — eliminates repetitive URL and view code.

  • Return appropriate HTTP status codes: 201 Created, 204 No Content for delete, 400 for validation errors.

  • Use select_related/prefetch_related in get_queryset() to avoid N+1 queries.

  • Handle exceptions with custom exception handlers in settings: EXCEPTION_HANDLER.

  • Use DRF Spectacular or drf-yasg to auto-generate OpenAPI/Swagger docs from your views.

  • Test with APIClient: client = APIClient(); client.force_authenticate(user=user); client.get("/api/articles/")

  • Throttle unauthenticated endpoints to prevent abuse — default anon: 100/day is a good start.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free