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:
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 = UserSerializerModelViewSet
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.
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 = UserSerializerReadOnlyModelViewSet
If you want to expose a read-only API, you can use ReadOnlyModelViewSet. It only includes the ListModelMixin and RetrieveModelMixin.
class ReadOnlyUserViewSet(viewsets.ReadOnlyModelViewSet):
"""
A viewset that provides only `list()` and `retrieve()` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializerCustom Actions
You can add custom endpoints to a ViewSet using the @action decorator.
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=Trueroutes to/users/{pk}/set_password/(acts on a single object).detail=Falseroutes 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.
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.
"""
passBy 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.
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.
"""
passWhichever 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!
