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.