Skip to content
thesarfo

Note

ViewSets

Combining a list view and a detail view into a single ModelViewSet, and using ReadOnlyModelViewSet for read-only resources.

views 0

Now if you look at our ProductList and ProductDetail views

class ProductList(ListCreateAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
def get_serializer_context(self):
return {'request': self.request}
class ProductDetail(RetrieveUpdateDestroyAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
def delete(self, request, pk):
product = get_object_or_404(Product, pk=pk)
if product.orderitems_set.count() > 0:
return Response({'error': 'Product cannot be deleted because it is associated with an order item'})
product.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

You can see that the logic we use to get our queryset and serializer_class is the same..hence more duplication. But using a viewset, we get to combine the logics in two different views into a single class. Thats why its called a viewset - a set of related views. see below of how we can combine the ProductList and ProductDetail views into a viewset.

from rest_framework.viewsets import ModelViewSet
class ProductViewSet(ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
def get_serializer_context(self):
return {'request': self.request}
def destroy(self, request, *args, **kwargs):
if OrderItem.objects.filter(product_id=kwargs['pk']).count() > 0:
return Response({'error': 'Product cannot be deleted because it is associated with an order item'})
return super().destroy(request, *args, **kwargs)

We inherit from ModelViewSet, because ModelViewSet contains all the logic for CRUD, and we also get our queryset and serializer_class. Then we add the context and override the delete method like we did before, the delete method has now been changed to destroy, because the ModelViewSet method for performing a deletion is destroy, so we edit our deletion logic a little bit. This way we let the ModelViewSet do everything for us.

In other words, we are using the ProductViewSet class as a single class to handle everything about our products/ endpoint. But in our url_patterns below, we are still referencing the ProductList and ProductDetail classes to handle our product endpoints.

urlpatterns = [
path('products/', views.ProductList.as_view()),
path('products/<int:pk>/', views.ProductDetail.as_view()),
path('collections/', views.CollectionList.as_view()),
path('collections/<int:pk>/', views.CollectionDetail.as_view(), name='collection-detail')
]

How do we make the ProductViewSet class handle the products/ endpoints, this is where Routers come into play.

Similarly, we can also use viewsets for our CollectionList and CollectionDetail classes.

class CollectionViewSet(ModelViewSet):
queryset = Collection.objects.annotate(products_count=Count('products')).all()
serializer_class = CollectionSerializer
def delete(self, request, pk):
collection = get_object_or_404(Collection, pk=pk)
if collection.products.count() > 0:
return Response({'error': 'Collection cannot be deleted because it includes one or more products'})
collection.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

Note that, when you inherit from the ModelViewSet class, you get to perform all forms of CRUD operations on a resource, both read and write. Now lets say you only want users to read it(GET) and not write on it. You can use a class called “ReadOnlyModelViewSet” which only allows read operations on a resource. so users can get all resources, or get a single one, but wont be able to UPDATE or DELETE or even POST to it.