CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-djangorestframework-stubs

PEP 484 type stubs for Django REST Framework enabling static type checking with comprehensive type definitions for all major DRF components

Pending
Overview
Eval results
Files

routers-urls.mddocs/

Routers & URLs

Django REST Framework routers provide automatic URL routing for ViewSets, enabling RESTful API endpoints with minimal configuration. The type stubs ensure type-safe URL pattern generation and ViewSet registration.

Router Classes

BaseRouter

class BaseRouter:
    """Base class for all DRF routers."""
    
    registry: list[tuple[str, type[ViewSetMixin], str]]
    
    def __init__(self) -> None: ...
    
    def register(
        self, 
        prefix: str, 
        viewset: type[ViewSetMixin], 
        basename: str | None = None
    ) -> None:
        """
        Register a viewset with the router.
        
        Args:
            prefix: URL prefix for the viewset routes
            viewset: ViewSet class to register
            basename: Base name for URL patterns (auto-generated if None)
        """
        ...
    
    def get_urls(self) -> list[URLPattern]:
        """
        Generate URL patterns for all registered viewsets.
        
        Returns:
            list[URLPattern]: Django URL patterns
        """
        ...
    
    def get_api_root_view(self, api_urls: list[URLPattern] | None = None) -> Callable: ...

Parameters:

  • prefix: str - URL prefix (e.g., 'books', 'api/v1/authors')
  • viewset: type[ViewSetMixin] - ViewSet class to register
  • basename: str | None - Base name for URL reversing (auto-detected if None)

SimpleRouter

class SimpleRouter(BaseRouter):
    """Simple router that generates standard RESTful routes."""
    
    routes: list[Route]
    
    def __init__(self, trailing_slash: bool = True) -> None: ...
    
    def get_default_basename(self, viewset: type[ViewSetMixin]) -> str: ...
    def get_lookup_regex(
        self, 
        viewset: type[ViewSetMixin], 
        lookup_prefix: str = ''
    ) -> str: ...

Parameters:

  • trailing_slash: bool - Add trailing slash to URLs (default: True)

DefaultRouter

class DefaultRouter(SimpleRouter):
    """Router that includes an API root view and browsable API."""
    
    include_root_view: bool
    include_format_suffixes: bool
    root_view_name: str
    
    def __init__(
        self,
        trailing_slash: bool = True,
        include_root_view: bool = True,
        include_format_suffixes: bool = True
    ) -> None: ...
    
    def get_api_root_view(self, api_urls: list[URLPattern] | None = None) -> type[APIRootView]: ...

Parameters:

  • include_root_view: bool - Include API root endpoint (default: True)
  • include_format_suffixes: bool - Support format suffixes like .json (default: True)
  • root_view_name: str - Name for the root view

Route Configuration

Route

class Route(NamedTuple):
    """Configuration for a standard route."""
    
    url: str
    mapping: dict[str, str]  
    name: str
    detail: bool
    initkwargs: dict[str, Any]

Fields:

  • url: str - URL pattern with format placeholders
  • mapping: dict[str, str] - HTTP methods to ViewSet actions mapping
  • name: str - URL name pattern
  • detail: bool - Whether this is a detail route (requires object lookup)
  • initkwargs: dict[str, Any] - Additional ViewSet initialization kwargs

DynamicRoute

class DynamicRoute(NamedTuple):
    """Configuration for custom @action routes."""
    
    url: str
    name: str
    detail: bool
    initkwargs: dict[str, Any]

APIRootView

class APIRootView(APIView):
    """Default API root view that lists available endpoints."""
    
    _ignore_model_permissions: bool
    schema: None
    api_root_dict: dict[str, str] | None
    
    def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

Router Usage Examples

Basic Router Setup

from rest_framework.routers import DefaultRouter
from rest_framework import viewsets

# Create router instance
router = DefaultRouter()

# Register viewsets
router.register(r'books', BookViewSet)
router.register(r'authors', AuthorViewSet)
router.register(r'categories', CategoryViewSet, basename='category')

# Get URL patterns
urlpatterns = router.urls

# Or include in main URLconf
from django.urls import path, include

urlpatterns = [
    path('api/', include(router.urls)),
    path('admin/', admin.site.urls),
]

Generated URL Patterns

# For BookViewSet registration, router generates:
# GET    /books/          -> BookViewSet.list()
# POST   /books/          -> BookViewSet.create()  
# GET    /books/{id}/     -> BookViewSet.retrieve()
# PUT    /books/{id}/     -> BookViewSet.update()
# PATCH  /books/{id}/     -> BookViewSet.partial_update()
# DELETE /books/{id}/     -> BookViewSet.destroy()

# URL names for reversing:
# book-list
# book-detail

Custom Basename

from django.contrib.auth.models import User

class UserViewSet(viewsets.ModelViewSet[User]):
    def get_queryset(self) -> QuerySet[User]:
        # Custom queryset logic
        return User.objects.filter(is_active=True)

# Register with custom basename since queryset is dynamic
router.register(r'users', UserViewSet, basename='user')

# Generated URL names: user-list, user-detail

Multiple Router Configuration

# API v1 router
v1_router = DefaultRouter()
v1_router.register(r'books', v1.BookViewSet)
v1_router.register(r'authors', v1.AuthorViewSet)

# API v2 router  
v2_router = DefaultRouter()
v2_router.register(r'books', v2.BookViewSet)
v2_router.register(r'authors', v2.AuthorViewSet)
v2_router.register(r'reviews', v2.ReviewViewSet)

# URL configuration
urlpatterns = [
    path('api/v1/', include((v1_router.urls, 'v1'), namespace='v1')),
    path('api/v2/', include((v2_router.urls, 'v2'), namespace='v2')),
]

ViewSet Actions & Routing

Standard Actions

class BookViewSet(viewsets.ModelViewSet[Book]):
    """Standard ModelViewSet with default actions."""
    
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
    # These methods are automatically routed:
    # list() -> GET /books/
    # create() -> POST /books/
    # retrieve() -> GET /books/{id}/
    # update() -> PUT /books/{id}/
    # partial_update() -> PATCH /books/{id}/
    # destroy() -> DELETE /books/{id}/

Custom Actions

from rest_framework.decorators import action
from rest_framework.response import Response

class BookViewSet(viewsets.ModelViewSet[Book]):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
    @action(detail=False, methods=['get'])
    def popular(self, request: Request) -> Response:
        """List popular books - generates GET /books/popular/"""
        popular_books = Book.objects.filter(rating__gte=4.0)
        serializer = self.get_serializer(popular_books, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['post', 'delete'])
    def favorite(self, request: Request, pk: str | None = None) -> Response:
        """Add/remove favorite - generates POST/DELETE /books/{id}/favorite/"""
        book = self.get_object()
        
        if request.method == 'POST':
            request.user.favorite_books.add(book)
            return Response({'status': 'favorited'})
        else:  # DELETE
            request.user.favorite_books.remove(book)
            return Response({'status': 'unfavorited'})
    
    @action(
        detail=False, 
        methods=['get'],
        url_path='by-author/(?P<author_id>[^/.]+)',
        url_name='by_author'
    )
    def books_by_author(self, request: Request, author_id: str | None = None) -> Response:
        """Custom URL pattern - generates GET /books/by-author/{author_id}/"""
        books = Book.objects.filter(author_id=author_id)
        serializer = self.get_serializer(books, many=True)
        return Response(serializer.data)

Action Parameters:

  • detail: bool - Whether action requires object lookup (True) or collection-level (False)
  • methods: list[str] - HTTP methods supported by the action
  • url_path: str - Custom URL path segment (defaults to method name)
  • url_name: str - Custom name for URL reversing (defaults to method name)

URL Utilities

Helper Functions

def escape_curly_brackets(url_path: str) -> str:
    """Escape curly brackets in URL paths for regex compilation."""
    ...

def flatten(list_of_lists: Iterable[Iterable[Any]]) -> Iterable[Any]:
    """Flatten nested iterables."""
    ...

URL Reversing

from django.urls import reverse

# Reverse standard ViewSet URLs
book_list_url = reverse('book-list')  # /api/books/
book_detail_url = reverse('book-detail', kwargs={'pk': 1})  # /api/books/1/

# Reverse custom action URLs
popular_books_url = reverse('book-popular')  # /api/books/popular/
favorite_url = reverse('book-favorite', kwargs={'pk': 1})  # /api/books/1/favorite/
by_author_url = reverse('book-by-author', kwargs={'author_id': 5})  # /api/books/by-author/5/

# With namespaces
v1_books_url = reverse('v1:book-list')
v2_books_url = reverse('v2:book-list')

Advanced Router Configuration

Custom Route Patterns

from rest_framework.routers import Route, DynamicRoute

class CustomRouter(SimpleRouter):
    """Router with custom route patterns."""
    
    routes = [
        # List route
        Route(
            url=r'^{prefix}/$',
            mapping={'get': 'list', 'post': 'create'},
            name='{basename}-list',
            detail=False,
            initkwargs={'suffix': 'List'}
        ),
        # Detail route
        Route(
            url=r'^{prefix}/{lookup}/$',
            mapping={
                'get': 'retrieve',
                'put': 'update', 
                'patch': 'partial_update',
                'delete': 'destroy'
            },
            name='{basename}-detail',
            detail=True,
            initkwargs={'suffix': 'Instance'}
        ),
        # Custom action route
        DynamicRoute(
            url=r'^{prefix}/{lookup}/{url_path}/$',
            name='{basename}-{url_name}',
            detail=True,
            initkwargs={}
        ),
    ]

Nested Routes

from rest_framework_nested import routers

# Parent router
router = routers.SimpleRouter()
router.register(r'authors', AuthorViewSet, basename='author')

# Nested router
authors_router = routers.NestedSimpleRouter(router, r'authors', lookup='author')
authors_router.register(r'books', BookViewSet, basename='author-books')

# Generated URLs:
# /authors/                    -> AuthorViewSet.list()
# /authors/{id}/               -> AuthorViewSet.retrieve()
# /authors/{id}/books/         -> BookViewSet.list() (filtered by author)
# /authors/{id}/books/{id}/    -> BookViewSet.retrieve()

Router with Custom Lookup

class BookViewSet(viewsets.ModelViewSet[Book]):
    """ViewSet with custom lookup field."""
    
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'isbn'  # Use ISBN instead of primary key
    lookup_url_kwarg = 'isbn'

# Router generates URLs like: /books/978-0123456789/
router.register(r'books', BookViewSet)

Conditional Registration

from django.conf import settings

router = DefaultRouter()

# Always register these
router.register(r'books', BookViewSet)
router.register(r'authors', AuthorViewSet)

# Conditionally register admin endpoints
if settings.DEBUG or settings.ENABLE_ADMIN_API:
    router.register(r'admin/users', AdminUserViewSet, basename='admin-user')
    router.register(r'admin/stats', AdminStatsViewSet, basename='admin-stats')

Testing Router URLs

URL Pattern Testing

from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APITestCase

class RouterURLTests(APITestCase):
    """Test router-generated URL patterns."""
    
    def test_book_list_url(self) -> None:
        """Test book list URL resolution."""
        url = reverse('book-list')
        self.assertEqual(url, '/api/books/')
    
    def test_book_detail_url(self) -> None:
        """Test book detail URL resolution."""
        url = reverse('book-detail', kwargs={'pk': 1})
        self.assertEqual(url, '/api/books/1/')
    
    def test_custom_action_url(self) -> None:
        """Test custom action URL resolution."""
        url = reverse('book-popular')
        self.assertEqual(url, '/api/books/popular/')
    
    def test_nested_action_url(self) -> None:
        """Test nested action URL resolution."""
        url = reverse('book-favorite', kwargs={'pk': 1})
        self.assertEqual(url, '/api/books/1/favorite/')

ViewSet Action Testing

class BookViewSetTests(APITestCase):
    """Test ViewSet actions through router URLs."""
    
    def setUp(self) -> None:
        self.book = Book.objects.create(title='Test Book', author='Test Author')
    
    def test_list_action(self) -> None:
        """Test list action via router URL."""
        url = reverse('book-list')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
    
    def test_detail_action(self) -> None:
        """Test retrieve action via router URL."""
        url = reverse('book-detail', kwargs={'pk': self.book.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
    
    def test_custom_action(self) -> None:
        """Test custom action via router URL."""
        url = reverse('book-popular')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

Performance Considerations

Route Optimization

# Use SimpleRouter for better performance when root view not needed
router = SimpleRouter(trailing_slash=False)  # Also saves URL matching

# Minimize registered viewsets
router.register(r'books', BookViewSet)
# Don't register unused viewsets

# Use basename when queryset is dynamic to avoid inspection
router.register(r'user-books', UserBookViewSet, basename='user-book')

URL Caching

from django.core.cache import cache

class CachedRouter(DefaultRouter):
    """Router with URL caching."""
    
    def get_urls(self) -> list[URLPattern]:
        cache_key = f"router_urls_{len(self.registry)}"
        urls = cache.get(cache_key)
        
        if urls is None:
            urls = super().get_urls()
            cache.set(cache_key, urls, timeout=3600)  # Cache for 1 hour
        
        return urls

This comprehensive routing system provides type-safe URL pattern generation and ViewSet registration, enabling confident API endpoint configuration with full mypy integration and automatic RESTful URL structure.

Install with Tessl CLI

npx tessl i tessl/pypi-djangorestframework-stubs

docs

authentication-permissions.md

exceptions-status.md

fields-relations.md

index.md

pagination-filtering.md

requests-responses.md

routers-urls.md

serializers.md

views-viewsets.md

tile.json