post-thumb

Introduction To viewset in Django REST Framework

In this tutorial we will learn avout the viewset in Django REST Framework.

In Django Rest Framework (DRF), a ViewSet is a class that provides CRUD (Create, Retrieve, Update, Delete) operations for a specific model. ViewSets are similar to Django's class-based views but are tailored specifically for APIs.

There are two types of ViewSets in DRF:

  1. ModelViewSet: This ViewSet provides default implementations for CRUD operations based on the model provided.

  2. ReadOnlyModelViewSet: This ViewSet provides read-only access to the model and implements only the retrieve and list operations.

ViewSet

Django REST Framework allows you to combine the logic for a set of related views in a single class called viewset. 

There are two main advantages of using a viewset over using a view class.

1.Repeated logic can be combined into a single class.

2.By using routers, we no longer need to deal with writing up the URL conf ourselves.

ViewSet  class 

A viewset calss simply a type of class based view , that doesnot provide any method handlers such as get(),post() abd instead provides such as list() and create(). Viewset is inherite from APIView.

.list() - get all records                                                          .update() -update record completely

.retrive() - get single record                                              .partial_update() -update record partially

.destory() - delete record                                                   .perform_update(self,serializer)

.create()- insert new record

.perform_create(self,serializer)

 

.perform_create(self,serializer)

perform create method is used to add extra information when creating a new object. perform_create() method will not execute if you override create() method.

example:

class PostViewSet(ModelViewSet):
    queryset = Post.objects.none()
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated,)
    http_method_names = ['post', ]

    def perform_create(self, serializer):
        self.request.data.get("title", None)  # read data from request
        if self.request.user.is_authenticated:
            instance = serializer.save(author=self.request.user)
        else:
            instance = serializer.save()   

.perform_update()

perform update method is used to add extra information when updating objects. perform_update() method will not execute if you override update() method.

example:

class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated,)
    http_method_names = ['patch', ]

    def perform_update(self, serializer):
        instance = self.get_object()  # instance before update
        self.request.data.get("title", None)  # read data from request
        if self.request.user.is_authenticated:
            updated_instance = serializer.save(author=self.request.user)
        else:
            updated_instance = serializer.save()

 

Example of ViewSet Structure:

from rest_framework import viewsets
class ListCreateUpdateDistoryAPIView(viewsets.ViewSet):
    permission_classes = (IsAuthenticated,)
    queryset = Company.objects.all()
    serializer_class = CompanySerializer
    
    def get_queryset(self):    

    def list(self, request):

    def retrieve(self, request, pk=None):
    
    def create(self, request, *args, **kwargs):
    
    def perform_create(self, serializer):

    def update(self, request, pk=None, *args, **kwargs):

    def perform_update(self, serializer):
        
    def partial_update(self, request, pk=None, *args, **kwargs):

    def destroy(self, request, pk=None, *args, **kwargs):

During the dispatch , the following attributes are available on the viewset.

1.basename  - the base to use for the URL names thar are created.

2.action         - the name of the current action like list(),create() e.t.c

3.detail          - boolean indicating if the current action is config used for lsit or detail view

4.suffix

5.name

6.description

Note:

For ViewSet you have to defile the viewset class like list(),create() as per the requirements but when you used ModelViewSet and ReadOnlyModelViewSet class then it is not mantadory to defile serializer class unless you need to customize . if you need oly one serializer in whole viewset then , you can define it on the upper layer and used it otherwise if separate serializer class is needed for the separate viewset class then you can defile it separatelt also too.

self.get_objects() method is not working for viewset class , it will work for modelviewset.

To use a ViewSet in DRF, you need to define the ViewSet class and then register it with a router. The router automatically generates the URLs for the ViewSet based on the actions defined in the ViewSet.

Here's an example of a basic ViewSet for a model named Company:

viewset URL config

from rest_framework import routers
from main import views
from django.urls import path,include
router = routers.DefaultRouter()
router.register(r'company', views.ListCreateUpdateDistoryAPIView,basename="company"),
router.register(r'permit', views.PermitAPIView,basename='permit'),
urlpatterns = [
    path('',include(router.urls)),
]

ViewSet Example:

from rest_framework import viewsets
from django.shortcuts import get_object_or_404
class ListCreateUpdateDistoryAPIView(viewsets.ViewSet):
    permission_classes = (IsAuthenticated,)
    queryset = Company.objects.all()
    serializer_class = CompanySerializer
    # http_method_names = ['get', ]
    def list(self, request):
        # query = self.request.GET.get('query', None)
        queryset = Company.objects.all()
        serializer = self.serializer_class(queryset, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def retrieve(self, request, pk=None):
        #method -1
        # queryset = Company.objects.all()
        # instance = get_object_or_404(queryset, pk=pk)
        #method -2
        instance= get_object_or_404(Company, pk=pk)
        #method -3
        # instance = self.get_object()
        #method -4 
        # instance=Company.objects.get(id=pk)
        serializer = self.serializer_class(instance)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
    def create(self, request, *args, **kwargs):
        user = request.user
        data=request.data
        serializer = self.serializer_class(data=data,context={'author': user})
        if serializer.is_valid():
            serializer.save()
            return Response(data=serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def update(self, request, pk=None, *args, **kwargs):
        # instance = self.get_object()
        instance=Company.objects.get(id=pk)
        # instance= get_object_or_404(Company, pk=pk)
        data =request.data
        serializer = self.serializer_class(instance=instance,data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(data=serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
    def partial_update(self, request, pk=None, *args, **kwargs):
        # instance = self.get_object()
        instance= get_object_or_404(Company, pk=pk)
        data =request.data
        serializer = self.serializer_class(instance=instance,data=data,partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(data=serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
    def destroy(self, request, pk=None, *args, **kwargs):
        # instance = self.get_object()
        instance= get_object_or_404(Company, pk=pk)
        instance.delete()
        return Response({"message":"Deleted successfully"})
        # return super(ListCreateUpdateDistoryAPIView, self).destroy(request, pk, *args, **kwargs)

 

Generic ViewSet

The GenericViewSet class inherits from GenericAPIView, and provides the default set of get_objectget_queryset methods and other generic view base behavior, but does not include any actions by default.

ModelViewSet

Model ViewSet Class inherits from GenericAPIView and includes implementations for various actions , by mixing in the behavior of the various mixin classes.

The actions provides by the modelviewset class are list(),create(),retrive(),update(),pearial_update() and destroy().

you can use any of the standard attributes or method overrides provided by GenericAPIView.

#urls.py
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'books', BookViewSet)

#views.py
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

ReadOnlyModelViewset Class

It is also inherits from GenericAPIView. As it is similar to the ModelViewSet but it only provide the read_only actions like list() and retrive(). you can use any of the standard attribute and method overrides available iin GenericAPIView.

class EmployeeListAPIView1(viewsets.ReadOnlyModelViewSet):
    queryset=Employee.objects.all()
    serializer_class=EmployeeSerializer
    pagination_class=CustomPagination

 

 

Extra action for routing

With @action decorator, extra actions may be intended for either a single object, or an entire collection. To indicate this, set the detail argument to True or False. The router will configure its URL patterns accordingly. e.g detail=True means detail actions to contain pk in their URL patterns.

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated, AllowAny  # NOQA
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework import status
from api.serializer import PostSerializer, PostAuthorSerializer
from myapp.models import Post


class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated,)
    http_method_names = ['get', ]

    def get_serializer_class(self):
        if self.action == "retrieve":
            return self.serializer_class
        elif self.action == "post_author":
            return PostAuthorSerializer
        elif self.action == "all_post_author":
            return PostAuthorSerializer
        else:
            return self.serializer_class

    def retrieve(self, request, pk=None):
        post = self.get_object()
        serializer = self.get_serializer(post)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @action(detail=True, methods=["get"], url_path=r'post-author',)
    def post_author(self, request, pk=None):
        post = self.get_object()
        serializer = self.get_serializer(post)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @action(detail=False, methods=["get"], url_path=r'all-post-author',)
    def all_post_author(self, request):
        post = Post.objects.all()
        serializer = self.get_serializer(post, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

API ends Points

url = api/post/13/
method call = retrieve()

url = api/post/13/post-author/
method call = post_author()

url = api/post/all-post-author/
method call = all_post_author()

Other way of making the serializer class dynamic is using the concept of inheritance like

class MultiSerializerViewSet(viewsets.ModelViewSet):
    serializers = {'default': None,}
    def get_serializer_class(self):
        return self.serializers.get(self.action, self.serializers['default'])

class PermitAPIView(MultiSerializerViewSet):
    queryset = Permit.objects.all()
    serializers = {
        'default': PermitSerializer,
        'list': PermitListSerializer,
        "retrieve": PermitDetailSerializer
    }