DRF Generic Views & Mixins
When a full ViewSet is too much, generic views give you one endpoint with exactly the behaviour you want.
ViewSets are great for uniform CRUD, but sometimes you want a single, explicit endpoint — a read-only list, or a create-only intake endpoint. DRF's generic views are the middle ground between raw APIView and a full ModelViewSet.
The building blocks
Each generic view combines GenericAPIView (which supplies get_queryset, get_serializer, pagination) with one or more mixins:
| Class | HTTP |
|---|---|
ListAPIView / CreateAPIView | GET list / POST |
RetrieveAPIView | GET one |
ListCreateAPIView | GET list + POST |
RetrieveUpdateDestroyAPIView | GET + PUT/PATCH + DELETE one |
from rest_framework.generics import ListCreateAPIView
class BookListCreate(ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# GET /books/ -> list
# POST /books/ -> createThey share every hook a ViewSet has (get_queryset, perform_create, filtering, pagination), so everything you learned there transfers directly. Choose generics when you want URLs and behaviour spelled out one endpoint at a time; choose ViewSets when a router-generated CRUD set is exactly right.
Example
from rest_framework import generics, filters
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
class ProductListCreateView(generics.ListCreateAPIView):
serializer_class = ProductSerializer
permission_classes = [IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['category', 'in_stock']
search_fields = ['name', 'sku']
ordering_fields = ['price', 'created']
ordering = ['-created']
def get_queryset(self):
# Only products belonging to the caller's organisation.
return (
Product.objects
.filter(org=self.request.user.org)
.select_related('category')
)
def perform_create(self, serializer):
serializer.save(org=self.request.user.org, created_by=self.request.user)
class ProductDetailView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = ProductSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Product.objects.filter(org=self.request.user.org)
def perform_destroy(self, instance):
# Soft-delete instead of a hard DELETE.
instance.is_active = False
instance.save(update_fields=['is_active'])
# urls.py
# urlpatterns = [
# path('products/', ProductListCreateView.as_view()),
# path('products/<int:pk>/', ProductDetailView.as_view()),
# ]When to use it
- A developer uses ListCreateAPIView to provide both GET (list) and POST (create) on a single endpoint class.
- A team uses RetrieveUpdateDestroyAPIView for a resource detail endpoint that supports all three methods.
- A developer overrides perform_create() in a generic view to automatically set the author from request.user.
More examples
ListCreateAPIView
Handles GET (list) and POST (create) on /posts/; perform_create injects the logged-in author.
from rest_framework.generics import ListCreateAPIView
class PostListCreateView(ListCreateAPIView):
queryset = Post.objects.filter(draft=False)
serializer_class = PostSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)RetrieveUpdateDestroyAPIView
Provides GET, PUT/PATCH, and DELETE on /posts/{pk}/ with ownership-based permission.
from rest_framework.generics import RetrieveUpdateDestroyAPIView
class PostDetailView(RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]Wire generic views in urls.py
Maps each generic view to a URL pattern; no router needed for simple two-URL REST resources.
from django.urls import path
from .views import PostListCreateView, PostDetailView
urlpatterns = [
path("posts/", PostListCreateView.as_view(), name="post-list"),
path("posts/<int:pk>/", PostDetailView.as_view(), name="post-detail"),
]
Discussion