Skip to content

ViewSets

While Generic Views reduce boilerplate to a great extent by providing pre-packaged CRUD operations for specific endpoints, ViewSets take this abstraction a step further.

A ViewSet allows you to combine the logic for multiple related views (such as a list view and a detail view) into a single class, keeping your code incredibly DRY. Instead of defining HTTP method handlers like get() or post(), you define higher-level actions like list(), retrieve(), create() and destroy().

When combined with a Router (discussed later), DRF will automatically generate the URL routing for all these actions, completely eliminating the need to write manual URL configurations.

GenericViewSet

The GenericViewSet class inherits from GenericAPIView but does not provide any default actions on its own. It simply provides the base methods like get_object() and get_queryset(). You can then mix in the specific behaviors you want:

python
from rest_framework import viewsets, mixins
from .models import User
from .serializers import UserSerializer

class CreateListUserViewSet(mixins.CreateModelMixin,
                            mixins.ListModelMixin,
                            viewsets.GenericViewSet):
    """
    A viewset that provides only `create()` and `list()` actions.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

ModelViewSet

The ModelViewSet class inherits from GenericViewSet and includes implementations for all standard CRUD actions by automatically mixing in the behavior of the various mixin classes.

python
from rest_framework import viewsets
from .models import User
from .serializers import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

ReadOnlyModelViewSet

If you want to expose a read-only API, you can use ReadOnlyModelViewSet. It only includes the ListModelMixin and RetrieveModelMixin.

python
class ReadOnlyUserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    A viewset that provides only `list()` and `retrieve()` actions.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

Custom Actions

You can add custom endpoints to a ViewSet using the @action decorator.

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

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=True, methods=['post'])
    def set_password(self, request, pk=None):
        user = self.get_object()
        # Custom logic here...
        return Response({'status': 'password set'})
  • detail=True routes to /users/{pk}/set_password/ (acts on a single object).
  • detail=False routes to /users/set_password/ (acts on a collection of objects).

The /users prefix in the URL examples above is not determined by the ViewSet itself, but by how you register the ViewSet with your Router (e.g., router.register(r'users', UserViewSet)).

NOTE

The methods argument in the @action decorator is optional. If you omit it, the custom action will default to accepting only GET requests.

Custom ViewSets

You can create custom base ViewSets to provide a reusable, specific set of operations across your project. There are two main ways to build them:

By Explicit Composition (GenericViewSet)

The cleanest way to build a custom ViewSet is to inherit from viewsets.GenericViewSet and explicitly mix in only the classes you want.

To see how powerful this is, let's build a custom base ViewSet that mimics DRF's ModelViewSet, but replaces the default hard-delete behavior with the custom SoftDeleteModelMixin we built previously on the Mixins page.

python
from rest_framework import viewsets, mixins
from myapp.mixins import SoftDeleteModelMixin

class SoftDeleteModelViewSet(mixins.CreateModelMixin,
                             mixins.ListModelMixin,
                             mixins.RetrieveModelMixin,
                             mixins.UpdateModelMixin,
                             SoftDeleteModelMixin,
                             viewsets.GenericViewSet):
    """
    Explicitly includes standard CRUD actions, but uses our custom soft delete.
    """
    pass

By Inheriting ModelViewSet

Alternatively, you can simply inherit directly from ModelViewSet and override its default behavior.

Since ModelViewSet already provides all the standard CRUD actions, you can inject your custom SoftDeleteModelMixin by placing it first in the inheritance list. Thanks to Python's Method Resolution Order (MRO), your mixin's destroy() method will take precedence over the default one.

python
from rest_framework import viewsets
from myapp.mixins import SoftDeleteModelMixin

class SoftDeleteModelViewSet(SoftDeleteModelMixin, viewsets.ModelViewSet):
    """
    Inherits all default ModelViewSet behavior, but the SoftDeleteModelMixin 
    takes precedence for the `destroy()` action.
    """
    pass

Whichever approach you choose, you can now inherit from SoftDeleteModelViewSet for any endpoint in your project where you want data preserved. This keeps your codebase extremely DRY and completely hides the complex implementation details from your everyday views!