Kolawole Mangabo

APIView vs ViewSets.

When building your API in Django using Django Rest Framework, you have two choices for writing the controllers: APIView or ViewSets.

APIView

APIView provides a methods handler for HTTP verbs: get, post, put, patch and delete. But you’ll need to write a method for each HTTP verb. For example, you are writing a view for a Product resources with a ProductSerializer.

Here’s an example of the View with a get() method to return all the products in the database.

from rest_framework.views import APIView


class ProductView(APIView):
    
    serializer_class = ProductSerializer
    
    def get(self, request, *args, **kwargs):
        
        products = Product.objects.all()
        
        serializer = self.serializer_class(products, many=True)
        
        return Response(serializer.data)

APIView works best for simple endpoints that won’t really require many possibles HTTP requests. What do I mean? Suppose that you want an endpoint to create a product. Pretty simple. We can quickly handle it.

from rest_framework.views import APIView


class ProductView(APIView):
    
    serializer_class = ProductSerializer
    
    def get(self, request, *args, **kwargs):
        
        products = Product.objects.all()
        
        serializer = self.serializer_class(products, many=True)
        
        return Response(serializer.data)
    
    def post(self, request, *args, **kwargs):
        
        serializer = self.serializer_class(data=request.data)
        
        serializer.is_valid(raise_exception=True)
        
        return Response(serializer.data)

We can now create a product, but let’s suppose we want to make a certain action on the product, like make it unavailable. To do it, a POST request must be sent to the endpoint and we’ll handle the rest with some methods on the Product model.

We can’t do that in the ProductView class because there is already a post() method. A solution? Add another APIView … and this starts becoming quite verbose.🤔 Not to add that you’ll have to add two different URL routes for the same resource, which can be a bad API design.

A solution? 💡 ViewSets.

ViewSets

A ViewSet is a class-based view, able to handle all of the basic HTTP requests: GET, POST, PUT, DELETE without hard coding any of the logic. The viewsets module also provides a ModelViewSet class that maps HTTP request actions to CRUD actions in the Database. For example, let’s rewrite the ProductView in a ModelViewSet.

class ProductViewSet(ModelViewSet):
    
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    http_method_names = ['get', 'post']

Well, this is doing the same job as the ProductView. Interesting, right?🤩 This is even more interesting when we want a certain action on the same resource, no need to add a new class.

class ProductViewSet(ModelViewSet):
...
    @action(detail=True, methods=['post'])
    def unavailable(self, request, *args, **kwargs):
        obj = self.get_object()
        
        obj.unavailable()
        
        return Response({'status': 'unavailable'})
...

And that’s not all. If you are working with viewsets, instead of URL you’ll be working with routers. And here’s how to register a viewsets in your router file.

router = routers.DefaultRouter()

router.register(r'products', ProductViewSet)

And with this, you’ll have the following structure for the products endpoint.

Endpoint HTTP Method Description
/products/ POST Creating a new product
/products/ GET Get the list of all users
/products/product_id/ GET Retrieve a product
/products/product_id/unavailable/ POST Make a product unavailable

Summary

APIView and ViewSet all have their right use cases. But most of the time, if you are only doing CRUD on resources, you can directly use ViewSets to respect the DRY principle. But if you are looking for more complex features, you can go low-level because after all, viewsets are also a subclass of APIView.

More on viewsets and views.