Django REST Framework ViewSets — Core Concepts
What ViewSets replace
Without ViewSets, building a REST API for a single model requires multiple views and URL patterns:
GET /articles/→ list all articles (one view)POST /articles/→ create an article (same or separate view)GET /articles/5/→ retrieve article 5 (another view)PUT /articles/5/→ update article 5 (same detail view)DELETE /articles/5/→ delete article 5 (same detail view)
That’s at least two views and two URL patterns per model. For 20 models, that’s 40+ views and URL configurations with repetitive code.
A ViewSet combines all these actions into a single class, and a router generates the URL patterns automatically.
ModelViewSet: the full package
from rest_framework import viewsets, serializers
from myapp.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author', 'published_at']
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
These 10 lines give you a complete CRUD API: list, create, retrieve, update, partial update, and delete. ModelViewSet inherits from mixins that implement each action.
Routers generate URLs
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
urlpatterns = [
path('api/', include(router.urls)),
]
This generates:
GET/POST /api/articles/GET/PUT/PATCH/DELETE /api/articles/{pk}/
The router also creates a browsable API root that lists all registered endpoints — useful during development.
ViewSet hierarchy
DRF provides several ViewSet classes for different needs:
ViewSet— bare base class, no default actions. Write everything from scratch.GenericViewSet— adds queryset and serializer handling but no actions.ModelViewSet— full CRUD: list, create, retrieve, update, destroy.ReadOnlyModelViewSet— only list and retrieve (no write operations).
You can also compose your own by mixing in specific actions:
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin
from rest_framework.viewsets import GenericViewSet
class ArticleViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# This gives list, retrieve, and create — but not update or delete
This selective composition is powerful when your API intentionally restricts operations.
Custom actions
ViewSets aren’t limited to CRUD. The @action decorator adds custom endpoints:
from rest_framework.decorators import action
from rest_framework.response import Response
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
article = self.get_object()
article.published_at = timezone.now()
article.save()
return Response({'status': 'published'})
@action(detail=False, methods=['get'])
def recent(self, request):
recent = self.queryset.order_by('-created_at')[:10]
serializer = self.get_serializer(recent, many=True)
return Response(serializer.data)
detail=Truecreates an endpoint on a specific object:POST /api/articles/5/publish/detail=Falsecreates a collection-level endpoint:GET /api/articles/recent/
Filtering and permissions
ViewSets integrate cleanly with DRF’s filtering and permission systems:
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django_filters.rest_framework import DjangoFilterBackend
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend]
filterset_fields = ['author', 'published_at']
Permissions apply to all actions by default. Override get_permissions() for per-action control:
def get_permissions(self):
if self.action in ['update', 'partial_update', 'destroy']:
return [IsAdminUser()]
return [IsAuthenticatedOrReadOnly()]
When not to use ViewSets
ViewSets work best for resource-oriented APIs where models map cleanly to endpoints. They’re less suitable when:
- Your endpoint doesn’t correspond to a single model (aggregations, multi-model operations)
- The request/response shapes differ significantly from your model structure
- You need very different logic per HTTP method
In these cases, DRF’s APIView or function-based @api_view give you full control without fighting ViewSet conventions.
Common misconception
Developers sometimes think ViewSets are magic that removes the need to understand REST. ViewSets automate boilerplate but don’t make design decisions for you. You still need to choose the right serializer fields, set proper permissions, handle pagination, and design meaningful URL structures. The automation handles plumbing, not architecture.
The one thing to remember: ViewSets combine related API actions into one class and pair with routers to eliminate URL boilerplate — pick ModelViewSet for full CRUD, compose mixins for selective operations, and fall back to APIView when the convention doesn’t fit.
See Also
- Python Django Admin Get an intuitive feel for Django Admin so Python behavior stops feeling unpredictable.
- Python Django Basics Get an intuitive feel for Django Basics so Python behavior stops feeling unpredictable.
- Python Django Celery Integration Why your Django app needs a helper to handle slow jobs in the background.
- Python Django Channels Websockets How Django can send real-time updates to your browser without you refreshing the page.
- Python Django Custom Management Commands How to teach Django new tricks by creating your own command-line shortcuts.