Skip to content

Django Rest Framework viewsets

Amin Zamani edited this page May 6, 2023 · 2 revisions

When and for what purpose do we use ViewSets in Django Rest Framework?

In Django REST framework, viewsets are a way to group related views into a single class based on the actions they perform on a model or a set of data. They provide a simple way to define the CRUD (Create, Read, Update, Delete) operations for a model or set of data.

Viewsets are commonly used in REST APIs to create a consistent interface for managing resources. They can handle complex serialization, deserialization, and validation logic, and can also provide authentication and permission checks.

Viewsets are particularly useful when working with related models, as they can provide a way to handle the different types of relationships between them. For example, you might have a viewset that handles the creation and retrieval of a user's orders, which would involve querying the order model and the user model.

Viewsets can also be used to define custom actions on a model or a set of data, such as sending a notification or performing a calculation. This provides a way to add custom functionality to an API without having to create a new endpoint.

1. What is a viewset in Django Rest Framework?

A ViewSet in Django Rest Framework is a class that defines the CRUD (Create, Read, Update, and Delete) operations for a particular model or a group of related models. It provides an easy and consistent way to handle common API operations, allowing you to define how URLs should be mapped to different actions in a single place. ViewSets can be used with routers to automatically generate URL patterns, reducing the amount of boilerplate code required to set up an API endpoint.

2. What is the difference between a ModelViewSet and a ViewSet?

In Django Rest Framework, a ModelViewSet is a subclass of GenericAPIView and provides default CRUD operations (create, retrieve, update, delete) for a Django model. It also includes support for listing objects, as well as creating and deleting objects in bulk.

On the other hand, a ViewSet is a more generic class that provides the basic behavior for view classes in Django Rest Framework. It does not provide any default implementations for CRUD operations, so you need to define them explicitly.

Therefore, a ModelViewSet is a more specialized type of ViewSet that is specifically designed for working with Django models and provides default implementations for the most common actions you might perform on a model.

3. What are the CRUD methods that a ModelViewSet provides by default?

A ModelViewSet provides the following CRUD methods by default:

  • list(): Retrieves a list of instances of the model.
  • create(): Creates a new instance of the model.
  • retrieve(): Retrieves a specific instance of the model.
  • update(): Updates a specific instance of the model.
  • partial_update(): Performs a partial update on a specific instance of the model.
  • destroy(): Deletes a specific instance of the model.

These methods correspond to the standard HTTP methods: GET, POST, PUT, PATCH, and DELETE.

4. How do you customize the queryset for a ModelViewSet?

You can customize the queryset for a ModelViewSet by overriding the get_queryset method. By default, the get_queryset method returns the queryset attribute of the viewset, which is typically set to the model's default queryset. However, you can override this method to filter or order the queryset based on your requirements.

For example, if you want to only return objects that belong to the authenticated user, you can override the get_queryset method as follows:

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

    def get_queryset(self):
        user = self.request.user
        if user.is_authenticated:
            return MyModel.objects.filter(user=user)
        else:
            return MyModel.objects.none()

In this example, the get_queryset method checks whether the user is authenticated and only returns objects that belong to the authenticated user. If the user is not authenticated, it returns an empty queryset.

5. How do you customize the serializer for a ModelViewSet?

To customize the serializer for a ModelViewSet in Django Rest Framework, you can define the serializer class for the viewset by setting the serializer_class attribute.

Here's an example of how to customize the serializer for a ModelViewSet:

from rest_framework import viewsets
from myapp.serializers import MyModelSerializer
from myapp.models import MyModel

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

In this example, we've defined a custom serializer class MyModelSerializer and set it as the serializer for the MyModelViewSet. Now, when we perform CRUD operations on the MyModel objects, Django Rest Framework will use our custom serializer instead of the default serializer.

You can also override the serializer class for specific actions by defining a get_serializer_class() method in your viewset:

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()

    def get_serializer_class(self):
        if self.action == 'list':
            return MyListModelSerializer
        else:
            return MyDetailModelSerializer

In this example, we've defined two custom serializers MyListModelSerializer and MyDetailModelSerializer, and we're using them for the list and retrieve actions respectively, while using the default MyModelSerializer for other actions.

6. How do you customize the pagination for a ModelViewSet?

To customize the pagination for a ModelViewSet, you can define a pagination_class attribute in your viewset. For example:

from rest_framework.pagination import LimitOffsetPagination

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    pagination_class = LimitOffsetPagination

Here, we define a MyModelViewSet that uses a LimitOffsetPagination pagination class. You can also create your own custom pagination class by subclassing one of the existing pagination classes and implementing the necessary methods.

Once you have defined the pagination class, you can use the standard pagination query parameters (limit and offset for LimitOffsetPagination, page and page_size for PageNumberPagination, etc.) to control the pagination behavior of your viewset.

7. What is the purpose of the get_serializer_class() method in a ModelViewSet?

The get_serializer_class() method is used in a ModelViewSet to dynamically determine the serializer class to use for different actions. This allows you to use different serializers for different actions, such as a different serializer for listing objects versus retrieving a single object. The default implementation of get_serializer_class() returns the serializer_class attribute of the view, but you can override it to provide more customized behavior. For example, you could have different serializers for different user roles or for different content types.

8. How do you define custom actions in a ViewSet?

Custom actions can be defined in a ViewSet by creating a method that implements the action, and decorating it with the @action decorator. The @action decorator takes a number of parameters, including detail, which specifies whether the action applies to a single object or a collection of objects, and methods, which specifies the HTTP methods that are allowed for the action. Here is an example of defining a custom action in a ViewSet:

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

class MyViewSet(viewsets.ViewSet):
    ...

    @action(detail=True, methods=['post'])
    def my_action(self, request, pk=None):
        """
        Perform a custom action on a single object.
        """
        ...
        return Response(...)

In this example, the my_action method is decorated with @action with detail=True, which means that the action applies to a single object. The methods parameter specifies that the action is allowed for POST requests. Inside the method, you can implement the custom action and return a response.

9. How do you handle permissions in a ViewSet?

In Django Rest Framework, you can handle permissions in a ViewSet by specifying the permission_classes attribute for the viewset. This attribute allows you to specify a list of permission classes that apply to all of the viewset's actions by default.

For example, to require that all users be authenticated to access any viewset action, you can use the IsAuthenticated permission class:

from rest_framework import permissions

class MyViewSet(viewsets.ViewSet):
    permission_classes = [permissions.IsAuthenticated]
    # ...

You can also specify permission classes on a per-action basis by using the @action decorator with the permission_classes argument:

from rest_framework.decorators import action
from rest_framework import permissions

class MyViewSet(viewsets.ViewSet):
    # ...

    @action(detail=True, permission_classes=[permissions.IsAdminUser])
    def my_action(self, request, pk=None):
        # ...

This would require that the user be an admin to access the my_action endpoint, regardless of the default permissions specified for the viewset.

Other built-in permission classes include AllowAny, IsAuthenticatedOrReadOnly, and DjangoModelPermissions. You can also create your own custom permission classes by subclassing BasePermission and implementing the has_permission and/or has_object_permission methods.

10. How do you handle authentication in a ViewSet?

To handle authentication in a ViewSet in Django Rest Framework, you can use the authentication_classes attribute to specify the authentication classes that should be used for the viewset. For example, to use token-based authentication, you can set authentication_classes = [TokenAuthentication] in the viewset.

Additionally, you can use the permission_classes attribute to specify the permission classes that should be used for the viewset. For example, to require that the user is authenticated, you can set permission_classes = [IsAuthenticated] in the viewset.

Here's an example:

from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet

from .models import MyModel
from .serializers import MyModelSerializer

class MyModelViewSet(ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

In this example, the MyModelViewSet viewset requires that the user is authenticated with a token-based authentication scheme, and also requires that the user is authenticated in order to access the viewset's methods.

11. How do you handle throttling in a ViewSet?

In Django Rest Framework, throttling can be added to a ViewSet by setting the throttle_classes attribute to a list of throttle classes.

Here is an example of how to add throttling to a ViewSet:

from rest_framework.throttling import UserRateThrottle
from rest_framework.viewsets import ModelViewSet
from myapp.models import MyModel
from myapp.serializers import MyModelSerializer

class MyModelViewSet(ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    throttle_classes = [UserRateThrottle]

In this example, the UserRateThrottle class is used to throttle requests based on the rate of requests made by each user.

Throttling can also be customized by creating a custom throttle class that extends one of the built-in throttle classes or the throttle.SimpleRateThrottle class, and then setting the throttle_classes attribute to include the custom throttle class.

12. How do you handle content negotiation in a ViewSet?

Content negotiation in a ViewSet is handled automatically by Django Rest Framework. By default, Django Rest Framework supports JSON and HTML content types. When a client makes a request to a ViewSet, Django Rest Framework checks the Accept header of the request to determine which content type the client prefers. It then serializes the response in the requested format and sends it back to the client.

If you want to support other content types, you can define custom renderers in the renderer_classes attribute of the ViewSet. For example, if you want to support XML, you can add the following renderer:

from rest_framework.renderers import XMLRenderer

class MyViewSet(viewsets.ViewSet):
    renderer_classes = [JSONRenderer, XMLRenderer]

With this configuration, if a client makes a request with an Accept header of application/xml, Django Rest Framework will use the XMLRenderer to serialize the response.

13. How do you handle errors in a ViewSet?

In Django Rest Framework, you can handle errors in a ViewSet by defining custom exception handlers. Here are the steps to do so:

  1. Create a new module named exceptions.py in your app directory.
  2. Define custom exception classes that inherit from APIException. For example:
from rest_framework.exceptions import APIException

class MyCustomException(APIException):
    status_code = 400
    default_detail = 'My custom error message.'
    default_code = 'my_custom_error'
  1. In your ViewSet, override the handle_exception method and handle your custom exceptions as needed. For example:
from .exceptions import MyCustomException
from rest_framework.views import exception_handler

class MyViewSet(viewsets.ModelViewSet):
    ...

    def handle_exception(self, exc):
        if isinstance(exc, MyCustomException):
            response = Response({'detail': str(exc)}, status=exc.status_code)
        else:
            response = exception_handler(exc, self.request)

        return response

In the example above, if a MyCustomException is raised, the handle_exception method will return a custom response with the error message and status code. Otherwise, it will delegate to the default exception handler by calling exception_handler.

By customizing the handle_exception method, you can handle various types of exceptions and return custom error responses as needed in your ViewSet.

14. How do you test a ViewSet?

To test a ViewSet in Django Rest Framework, you can use the built-in test client and APIRequestFactory provided by Django. Here are the basic steps:

  1. Import the necessary modules:
from django.test import TestCase, Client
from rest_framework.test import APIClient, APIRequestFactory
  1. Define a test case:
class MyViewSetTestCase(TestCase):
    def setUp(self):
        self.factory = APIRequestFactory()
        self.client = APIClient()
    # Add test methods below
  1. Define a test method for each action in the ViewSet. For example:
def test_list(self):
    request = self.factory.get('/myviewset/')
    response = MyViewSet.as_view({'get': 'list'})(request)
    self.assertEqual(response.status_code, 200)
  1. Run the tests using the Django test runner:
python manage.py test app.tests.MyViewSetTestCase

This will run the MyViewSetTestCase test case and output the results in the console.

You can also use other test frameworks like pytest and unittest.mock to test a ViewSet in Django Rest Framework.

15. What is the purpose of the queryset attribute in a ModelViewSet?

The queryset attribute in a ModelViewSet is used to specify the queryset used to retrieve objects in each of the CRUD methods provided by the viewset. By default, the queryset attribute is set to the all() method on the model manager for the model associated with the viewset.

For example, if the queryset attribute is set to MyModel.objects.filter(is_active=True), then the list() method will return a list of all MyModel instances where is_active=True, and the create(), retrieve(), update(), and delete() methods will operate on instances from that queryset.

The queryset attribute can be customized to filter the list of objects returned or to control the order in which objects are returned. It can also be used to provide additional functionality, such as pagination or filtering.

16. What is the purpose of the serializer_class attribute in a ModelViewSet?

The serializer_class attribute in a ModelViewSet specifies the serializer class that should be used for serializing and deserializing the model instances. When a viewset action is called, such as list() or retrieve(), Django Rest Framework will automatically instantiate an instance of the serializer class specified by the serializer_class attribute and pass the relevant queryset or object instance to it for serialization or deserialization.

By default, if serializer_class is not set, Django Rest Framework will automatically determine the serializer class based on the viewset action being called and the type of queryset or object being serialized or deserialized. However, by setting the serializer_class attribute, you can explicitly specify the serializer class to be used for each viewset action.

17. How do you use the get_object() method in a ViewSet?

The get_object() method is used in a ViewSet to retrieve a specific object instance based on the provided lookup parameters, and to raise a 404 Not Found error if the object does not exist.

By default, the get_object() method is called by the detail actions (retrieve, update, partial_update, destroy) to retrieve the object to be acted upon. It can also be called by custom actions to retrieve an object for further processing.

Here's an example of how to use the get_object() method in a ViewSet:

from rest_framework import viewsets

class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MySerializer

    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())
        obj = queryset.get(pk=self.kwargs['pk'])
        # Perform any additional checks on the object here
        return obj

In this example, the get_object() method is overridden to retrieve a MyModel instance based on the pk URL parameter. The filter_queryset() method is called to apply any relevant filters to the queryset, and the get() method is called to retrieve the object instance. If the object does not exist, a 404 Not Found exception is raised.

Note that you can customize the get_object() method to retrieve objects based on other lookup fields, such as a slug or a unique identifier.

18. What is the purpose of the paginate_queryset() method in a ViewSet?

The paginate_queryset() method in a ViewSet is used to paginate the queryset based on the pagination class set in the pagination_class attribute. This method is called after the initial queryset has been filtered based on the request, but before it is serialized to a response.

The default implementation of paginate_queryset() simply returns the entire queryset without any pagination. However, this method can be overridden to provide custom pagination logic. For example, you can implement pagination based on page number or limit/offset parameters in the request, or implement a custom pagination algorithm.

Here's an example of how you can override paginate_queryset() to implement pagination based on the page query parameter:

from rest_framework.pagination import PageNumberPagination

class MyPagination(PageNumberPagination):
    page_size = 10

class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MySerializer
    pagination_class = MyPagination
    
    def paginate_queryset(self, queryset):
        """
        Paginate the queryset based on the `page` query parameter.
        """
        page_size = self.get_page_size(self.request)
        paginator = self.pagination_class()
        page = paginator.paginate_queryset(queryset, self.request)
        if page is not None:
            return page[:page_size]
        return None

In this example, we define a custom pagination class MyPagination which uses a page size of 10. We then set the pagination_class attribute to our custom pagination class in the viewset.

We override the paginate_queryset() method to extract the page query parameter from the request and use the paginator object to paginate the queryset. Finally, we return only the first page_size items from the paginated queryset.

19. How do you filter a ModelViewSet based on query parameters?

In Django Rest Framework, you can filter a ModelViewSet based on query parameters using the filter_queryset() method. The filter_queryset() method is called before paginating the queryset and is responsible for filtering the queryset based on the request parameters.

Here's an example of how to filter a ModelViewSet based on a query parameter:

from rest_framework import viewsets

class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

    def filter_queryset(self, queryset):
        params = self.request.query_params

        # Filter the queryset based on the `name` query parameter
        if 'name' in params:
            queryset = queryset.filter(name=params['name'])

        return queryset

In this example, the filter_queryset() method checks if the name query parameter is present in the request. If it is, the queryset is filtered to include only objects with the matching name. The filtered queryset is then returned, and the ModelViewSet will paginate and serialize the filtered results.

20. What is the purpose of the get_queryset() method in a ViewSet?

The get_queryset() method is a method provided by the ViewSet class in Django Rest Framework. It returns the queryset that will be used to retrieve objects from the database for the view. By default, the get_queryset() method returns the queryset specified in the queryset attribute of the view. However, you can override this method to provide custom queryset logic, such as filtering or sorting the results based on request parameters or user permissions. This method is called when a request is made to the view and is used to retrieve the data that will be returned in the response.

Python

Python Essentials 1 (PCEP)

Introduction to Python and computer programming

Data types, variables, basic I/O operations, and basic operators

Boolean values, conditional execution, loops, lists and list processing, logical and bitwise operations

Clean Code

Algorithms

Django

Django Rest Framework

API

pip

SQLAlchemy

FastAPI

Pytest

TDD

Git

Linux

Docker

Python Testing

Interview Questions

Clone this wiki locally