PEP 484 type stubs for Django REST Framework enabling static type checking with comprehensive type definitions for all major DRF components
—
Django REST Framework views and viewsets provide the core infrastructure for handling HTTP requests and returning responses. The type stubs enable full type safety for view classes, mixins, and viewset operations with comprehensive generic type support.
from typing import Any, NoReturn, Sequence, Callable
from django.http import HttpRequest, HttpResponseBase
from django.db.models import QuerySet, Manager
from rest_framework.views import APIView
from rest_framework.generics import GenericAPIView, UsesQuerySet
from rest_framework.mixins import (
CreateModelMixin, ListModelMixin, RetrieveModelMixin,
UpdateModelMixin, DestroyModelMixin
)
from rest_framework.viewsets import ViewSet, GenericViewSet, ModelViewSet
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import _PermissionClass, _SupportsHasPermission
from rest_framework.renderers import BaseRenderer
from rest_framework.parsers import BaseParser
from rest_framework.throttling import BaseThrottle
from rest_framework.negotiation import BaseContentNegotiation
from rest_framework.metadata import BaseMetadata
from rest_framework.versioning import BaseVersioning
from rest_framework.serializers import BaseSerializer
from rest_framework.filters import BaseFilterBackend
from rest_framework.pagination import BasePagination
from rest_framework.decorators import ViewSetAction
# Type variables
_MT_co = TypeVar("_MT_co", bound=Model, covariant=True)
_ViewFunc = Callable[..., HttpResponseBase]class APIView(View):
"""Base class for all DRF views with comprehensive type safety."""
# Class-level configuration
authentication_classes: Sequence[type[BaseAuthentication]]
permission_classes: Sequence[_PermissionClass]
renderer_classes: Sequence[type[BaseRenderer]]
parser_classes: Sequence[type[BaseParser]]
throttle_classes: Sequence[type[BaseThrottle]]
content_negotiation_class: type[BaseContentNegotiation]
metadata_class: str | BaseMetadata | None
versioning_class: type[BaseVersioning] | None
# Request processing
def dispatch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def handle_exception(self, exc: Exception) -> Response: ...
def permission_denied(self, request: Request, message: str | None = None, code: str | None = None) -> NoReturn: ...
def throttled(self, request: Request, wait: float) -> NoReturn: ...
# Configuration methods
def get_authenticators(self) -> list[BaseAuthentication]: ...
def get_permissions(self) -> Sequence[_SupportsHasPermission]: ...
def get_renderers(self) -> list[BaseRenderer]: ...
def get_parsers(self) -> list[BaseParser]: ...
def get_throttles(self) -> list[BaseThrottle]: ...
def get_content_negotiator(self) -> BaseContentNegotiation: ...
def get_exception_handler(self) -> Callable: ...
# HTTP method handlers
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def put(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def head(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def options(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def trace(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class GenericAPIView(APIView, UsesQuerySet[_MT_co]):
"""Generic view with model type support and common functionality."""
# Model and serializer configuration
queryset: QuerySet[_MT_co] | Manager[_MT_co] | None
serializer_class: type[BaseSerializer[_MT_co]] | None
# Lookup configuration
lookup_field: str # Default: 'pk'
lookup_url_kwarg: str | None
# Filtering and pagination
filter_backends: Sequence[type[BaseFilterBackend | BaseFilterProtocol[_MT_co]]]
pagination_class: type[BasePagination] | None
# Object retrieval
def get_object(self) -> _MT_co: ...
def get_queryset(self) -> QuerySet[_MT_co]: ...
def filter_queryset(self, queryset: QuerySet[_MT_co]) -> QuerySet[_MT_co]: ...
def paginate_queryset(self, queryset: QuerySet[_MT_co] | Sequence[Any]) -> Sequence[Any] | None: ...
def get_paginated_response(self, data: Any) -> Response: ...
# Serializer methods
def get_serializer(self, *args: Any, **kwargs: Any) -> BaseSerializer[_MT_co]: ...
def get_serializer_class(self) -> type[BaseSerializer[_MT_co]]: ...
def get_serializer_context(self) -> dict[str, Any]: ...Parameters:
queryset: QuerySet[_MT_co] | Manager[_MT_co] | None - Base queryset for the viewserializer_class: type[BaseSerializer[_MT_co]] | None - Serializer class for the modellookup_field: str - Model field for object lookup (default: 'pk')lookup_url_kwarg: str | None - URL kwarg name for lookup (defaults to lookup_field)class CreateModelMixin:
"""Mixin for creating model instances."""
def create(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def perform_create(self: UsesQuerySet[_MT], serializer: BaseSerializer[_MT]) -> None: ...
def get_success_headers(self, data: Any) -> dict[str, str]: ...class ListModelMixin:
"""Mixin for listing model instances."""
def list(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class RetrieveModelMixin:
"""Mixin for retrieving a model instance."""
def retrieve(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class UpdateModelMixin:
"""Mixin for updating model instances."""
def update(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def partial_update(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def perform_update(self: UsesQuerySet[_MT], serializer: BaseSerializer[_MT]) -> None: ...class DestroyModelMixin:
"""Mixin for deleting model instances."""
def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def perform_destroy(self: UsesQuerySet[_MT], instance: _MT) -> None: ...class CreateAPIView(CreateModelMixin, GenericAPIView[_MT_co]):
"""Concrete view for creating model instances."""
def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class ListAPIView(ListModelMixin, GenericAPIView[_MT_co]):
"""Concrete view for listing model instances."""
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class RetrieveAPIView(RetrieveModelMixin, GenericAPIView[_MT_co]):
"""Concrete view for retrieving a model instance."""
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class UpdateAPIView(UpdateModelMixin, GenericAPIView[_MT_co]):
"""Concrete view for updating model instances."""
def put(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class DestroyAPIView(DestroyModelMixin, GenericAPIView[_MT_co]):
"""Concrete view for deleting model instances."""
def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class ListCreateAPIView(ListModelMixin, CreateModelMixin, GenericAPIView[_MT_co]):
"""List and create model instances."""
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
class RetrieveUpdateAPIView(RetrieveModelMixin, UpdateModelMixin, GenericAPIView[_MT_co]):
"""Retrieve and update model instances."""
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def put(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
class RetrieveDestroyAPIView(RetrieveModelMixin, DestroyModelMixin, GenericAPIView[_MT_co]):
"""Retrieve and delete model instances."""
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
class RetrieveUpdateDestroyAPIView(
RetrieveModelMixin,
UpdateModelMixin,
DestroyModelMixin,
GenericAPIView[_MT_co]
):
"""Full CRUD operations for model instances."""
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def put(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...
def delete(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...class ViewSetMixin:
"""Base mixin that converts a ViewSet class into a concrete view."""
# Class variables assigned in as_view()
name: str | None
description: str | None
suffix: str | None
detail: bool
basename: str
# Instance attributes assigned in view wrapper
action_map: dict[str, str]
args: tuple[Any, ...]
kwargs: dict[str, Any]
# Assigned in initialize_request()
action: str
@classmethod
def as_view(
cls,
actions: dict[str, str | ViewSetAction] | None = None,
**initkwargs: Any
) -> AsView[GenericView]: ...
def initialize_request(self, request: HttpRequest, *args: Any, **kwargs: Any) -> Request: ...
def reverse_action(self, url_name: str, *args: Any, **kwargs: Any) -> str: ...
@classmethod
def get_extra_actions(cls) -> list[_ViewFunc]: ...
def get_extra_action_url_map(self) -> dict[str, str]: ...class ViewSet(ViewSetMixin, APIView):
"""Base ViewSet class providing the interface for routing multiple actions."""
passclass GenericViewSet(ViewSetMixin, GenericAPIView[_MT_co]):
"""Generic ViewSet with model type support."""
passclass ReadOnlyModelViewSet(
RetrieveModelMixin,
ListModelMixin,
GenericViewSet[_MT_co]
):
"""ViewSet that provides read-only actions."""
pass
class ModelViewSet(
CreateModelMixin,
RetrieveModelMixin,
UpdateModelMixin,
DestroyModelMixin,
ListModelMixin,
GenericViewSet[_MT_co]
):
"""ViewSet that provides full CRUD actions."""
passfrom rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
class BookSearchView(APIView):
"""Custom view for searching books."""
permission_classes = [IsAuthenticated]
def get(self, request: Request) -> Response:
query = request.query_params.get('q', '')
if not query:
return Response(
{'error': 'Query parameter q is required'},
status=status.HTTP_400_BAD_REQUEST
)
books = Book.objects.filter(title__icontains=query)
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
def post(self, request: Request) -> Response:
"""Advanced search with filters."""
serializer = BookSearchSerializer(data=request.data)
if serializer.is_valid():
# Perform complex search logic
results = self.perform_search(serializer.validated_data)
return Response(results)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def perform_search(self, filters: dict[str, Any]) -> list[dict[str, Any]]:
"""Perform the actual search operation."""
queryset = Book.objects.all()
if 'genre' in filters:
queryset = queryset.filter(genre=filters['genre'])
if 'author' in filters:
queryset = queryset.filter(author__name__icontains=filters['author'])
if 'published_after' in filters:
queryset = queryset.filter(published_date__gte=filters['published_after'])
serializer = BookSerializer(queryset, many=True)
return serializer.datafrom rest_framework import generics
from rest_framework.permissions import IsAuthenticated
class BookListCreateView(generics.ListCreateAPIView[Book]):
"""List books and create new books."""
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self) -> QuerySet[Book]:
"""Filter books by current user if requested."""
queryset = super().get_queryset()
user_only = self.request.query_params.get('user_only', 'false')
if user_only.lower() == 'true':
return queryset.filter(created_by=self.request.user)
return queryset
def perform_create(self, serializer: BookSerializer) -> None:
"""Set the created_by field to current user."""
serializer.save(created_by=self.request.user)from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
class BookViewSet(viewsets.ModelViewSet[Book]):
"""Full CRUD operations for books."""
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_permissions(self) -> list[BasePermission]:
"""Different permissions for different actions."""
if self.action in ['create', 'update', 'partial_update', 'destroy']:
permission_classes = [IsAuthenticated]
else:
permission_classes = []
return [permission() for permission in permission_classes]
def get_serializer_class(self) -> type[BaseSerializer[Book]]:
"""Different serializers for different actions."""
if self.action == 'list':
return BookListSerializer
elif self.action in ['create', 'update', 'partial_update']:
return BookWriteSerializer
return BookDetailSerializer
@action(detail=True, methods=['post'])
def favorite(self, request: Request, pk: str | None = None) -> Response:
"""Add book to user's favorites."""
book = self.get_object()
user = request.user
if user in book.favorited_by.all():
return Response(
{'detail': 'Already favorited'},
status=status.HTTP_400_BAD_REQUEST
)
book.favorited_by.add(user)
return Response({'detail': 'Added to favorites'})
@action(detail=True, methods=['delete'])
def unfavorite(self, request: Request, pk: str | None = None) -> Response:
"""Remove book from user's favorites."""
book = self.get_object()
user = request.user
if user not in book.favorited_by.all():
return Response(
{'detail': 'Not in favorites'},
status=status.HTTP_400_BAD_REQUEST
)
book.favorited_by.remove(user)
return Response({'detail': 'Removed from favorites'})
@action(detail=False, methods=['get'])
def popular(self, request: Request) -> Response:
"""Get popular books based on favorites count."""
popular_books = Book.objects.annotate(
favorites_count=Count('favorited_by')
).order_by('-favorites_count')[:10]
serializer = self.get_serializer(popular_books, many=True)
return Response(serializer.data)from rest_framework.decorators import action
class AuthorViewSet(viewsets.ModelViewSet[Author]):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
@action(detail=True, methods=['get'])
def books(self, request: Request, pk: str | None = None) -> Response:
"""Get all books by this author."""
author = self.get_object()
books = author.book_set.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def statistics(self, request: Request, pk: str | None = None) -> Response:
"""Get author statistics."""
author = self.get_object()
stats = {
'total_books': author.book_set.count(),
'average_rating': author.book_set.aggregate(
avg_rating=Avg('rating')
)['avg_rating'] or 0,
'latest_book': author.book_set.order_by('-published_date').first().title
}
return Response(stats)
@action(detail=False, methods=['get'], url_path='by-genre/(?P<genre>[^/.]+)')
def by_genre(self, request: Request, genre: str | None = None) -> Response:
"""Get authors who have written books in a specific genre."""
authors = Author.objects.filter(book__genre=genre).distinct()
serializer = self.get_serializer(authors, many=True)
return Response(serializer.data)class ProtectedBookView(generics.RetrieveUpdateDestroyAPIView[Book]):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def get_permissions(self) -> list[BasePermission]:
"""Custom permission logic based on action."""
if self.request.method in ['PUT', 'PATCH', 'DELETE']:
permission_classes = [IsAuthenticated, IsOwner]
else:
permission_classes = [AllowAny]
return [permission() for permission in permission_classes]from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
class BookListView(generics.ListAPIView[Book]):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['genre', 'author', 'published_date']
search_fields = ['title', 'description', 'author__name']
ordering_fields = ['title', 'published_date', 'rating']
ordering = ['-published_date']
pagination_class = PageNumberPaginationdef get_view_name(view: APIView) -> str:
"""Get a human-readable name for the view."""
...
def get_view_description(view: APIView, html: bool = False) -> str:
"""Get a human-readable description for the view."""
...
def set_rollback() -> None:
"""Mark the current transaction as rollback-only."""
...
def exception_handler(exc: Exception, context: dict[str, Any]) -> Response | None:
"""Default exception handler for DRF views."""
...class OptimizedBookViewSet(viewsets.ModelViewSet[Book]):
serializer_class = BookSerializer
def get_queryset(self) -> QuerySet[Book]:
"""Optimize queries with select_related and prefetch_related."""
return Book.objects.select_related('author', 'publisher') \
.prefetch_related('genres', 'reviews')
def list(self, request: Request, *args: Any, **kwargs: Any) -> Response:
"""Custom list method with query optimization."""
queryset = self.get_queryset()
# Add any additional filtering
search = request.query_params.get('search')
if search:
queryset = queryset.filter(
Q(title__icontains=search) |
Q(description__icontains=search)
)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)from django.core.cache import cache
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
class CachedBookListView(generics.ListAPIView[Book]):
queryset = Book.objects.all()
serializer_class = BookSerializer
@method_decorator(cache_page(60 * 15)) # Cache for 15 minutes
def dispatch(self, *args: Any, **kwargs: Any) -> Response:
return super().dispatch(*args, **kwargs)
def list(self, request: Request, *args: Any, **kwargs: Any) -> Response:
cache_key = f"book_list_{request.GET.urlencode()}"
cached_response = cache.get(cache_key)
if cached_response is not None:
return Response(cached_response)
response = super().list(request, *args, **kwargs)
cache.set(cache_key, response.data, timeout=60 * 15)
return responseThis comprehensive view and viewset system provides type-safe HTTP request handling with full generic type support, enabling confident API development with complete mypy integration.
Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework-stubs