Web APIs for Django, made easy.
—
Automatic URL pattern generation for ViewSets with support for nested routes and custom actions in Django REST Framework.
Router classes automatically generate URL patterns from ViewSet registrations.
class BaseRouter:
"""
Base class for all router implementations.
"""
def __init__(self):
self.registry = [] # List of registered viewsets
def register(self, prefix, viewset, basename=None):
"""
Register a viewset with the router.
Args:
prefix (str): URL prefix for the viewset
viewset: ViewSet class to register
basename (str): Base name for URL patterns
"""
def get_urls(self):
"""
Generate URL patterns for all registered viewsets.
Returns:
list: Django URL patterns
"""
def get_api_root_view(self, api_urls=None):
"""
Create API root view showing available endpoints.
Args:
api_urls: URL patterns to include in root
Returns:
APIView: Root view class
"""
def get_default_basename(self, viewset):
"""
Get default basename from viewset's queryset model.
Args:
viewset: ViewSet class
Returns:
str: Default basename
"""
class SimpleRouter(BaseRouter):
"""
Simple router generating basic URL patterns.
"""
routes = [ # URL pattern templates
# List route: /prefix/
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={'get': 'list', 'post': 'create'},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
# Detail route: /prefix/{lookup}/
Route(
url=r'^{prefix}/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
]
def __init__(self, trailing_slash=True):
"""
Args:
trailing_slash (bool): Include trailing slash in URLs
"""
self.trailing_slash = '/' if trailing_slash else ''
super().__init__()
class DefaultRouter(SimpleRouter):
"""
Default router with API root view and optional format suffixes.
"""
include_root_view = True # Include API root endpoint
include_format_suffixes = True # Include format suffixes (.json, .xml)
root_view_name = 'api-root' # Name for root view
default_schema_renderers = None # Schema renderers for root view
def get_api_root_view(self, api_urls=None):
"""
Create browsable API root view.
Returns:
APIRootView: Root view showing available endpoints
"""Classes defining URL route patterns and mappings.
Route = namedtuple('Route', [
'url', # URL pattern template
'mapping', # HTTP method to viewset action mapping
'name', # URL pattern name template
'detail', # Whether route is for detail views
'initkwargs' # Additional viewset initialization kwargs
])
DynamicRoute = namedtuple('DynamicRoute', [
'url', # URL pattern template
'name', # URL pattern name template
'detail', # Whether route is for detail views
'initkwargs' # Additional viewset initialization kwargs
])Automatically generated root view for browsable API.
class APIRootView(APIView):
"""
Default API root view providing links to registered endpoints.
"""
_ignore_model_permissions = True
schema = None # Disable schema generation for root
api_root_dict = None # Dictionary of available endpoints
def get(self, request, *args, **kwargs):
"""
Return links to all available API endpoints.
Returns:
Response: Dictionary of endpoint names and URLs
"""Utilities for working with URL patterns and format suffixes.
def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
"""
Add format suffix patterns to existing URL patterns.
Args:
urlpatterns (list): Existing URL patterns
suffix_required (bool): Whether format suffix is required
allowed (list): List of allowed format suffixes
Returns:
list: URL patterns with format suffix support
"""
def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, allowed):
"""
Apply suffix patterns to URL patterns.
Args:
urlpatterns (list): URL patterns to modify
suffix_pattern (str): Suffix pattern template
suffix_required (bool): Whether suffix is required
allowed (list): Allowed suffix values
Returns:
list: Modified URL patterns
"""# urls.py
from rest_framework.routers import DefaultRouter
from myapp.views import BookViewSet, AuthorViewSet
# Create router instance
router = DefaultRouter()
# Register viewsets
router.register(r'books', BookViewSet)
router.register(r'authors', AuthorViewSet)
# Include router URLs
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls')),
]
# Generated URLs:
# /api/ (API root)
# /api/books/ (list/create books)
# /api/books/{id}/ (retrieve/update/delete book)
# /api/authors/ (list/create authors)
# /api/authors/{id}/ (retrieve/update/delete author)from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
@action(detail=True, methods=['post'])
def set_favorite(self, request, pk=None):
"""Mark book as favorite for current user."""
book = self.get_object()
# Custom logic here
return Response({'status': 'favorite set'})
@action(detail=False, methods=['get'])
def recent(self, request):
"""Get recently published books."""
recent_books = self.queryset.filter(
publication_date__gte=timezone.now() - timedelta(days=30)
)
serializer = self.get_serializer(recent_books, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'], url_path='reviews')
def book_reviews(self, request, pk=None):
"""Get reviews for specific book."""
book = self.get_object()
reviews = book.reviews.all()
# Serialize and return reviews
return Response(review_data)
# Register with custom basename
router.register(r'books', BookViewSet, basename='book')
# Generated URLs include custom actions:
# /api/books/{id}/set_favorite/ (POST)
# /api/books/recent/ (GET)
# /api/books/{id}/reviews/ (GET)# Main router for primary resources
main_router = DefaultRouter()
main_router.register(r'books', BookViewSet)
main_router.register(r'authors', AuthorViewSet)
# Secondary router for admin resources
admin_router = SimpleRouter()
admin_router.register(r'users', UserViewSet)
admin_router.register(r'logs', LogViewSet)
# Combine routers
urlpatterns = [
path('api/v1/', include(main_router.urls)),
path('api/admin/', include(admin_router.urls)),
# Manual URL patterns can be mixed in
path('api/auth/', include('rest_framework.authtoken.urls')),
]from rest_framework.routers import Route, DynamicRoute, SimpleRouter
class CustomRouter(SimpleRouter):
"""
Custom router with additional route patterns.
"""
routes = [
# Standard list route
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={'get': 'list', 'post': 'create'},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
# Custom bulk route
Route(
url=r'^{prefix}/bulk{trailing_slash}$',
mapping={'post': 'bulk_create', 'patch': 'bulk_update'},
name='{basename}-bulk',
detail=False,
initkwargs={}
),
# Standard detail route
Route(
url=r'^{prefix}/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
# Dynamic routes for @action decorated methods
DynamicRoute(
url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
),
DynamicRoute(
url=r'^{prefix}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=False,
initkwargs={}
),
]
# Usage
custom_router = CustomRouter()
custom_router.register(r'books', BookViewSet)
# Now supports bulk operations:
# POST /api/books/bulk/ -> bulk_create
# PATCH /api/books/bulk/ -> bulk_updatefrom rest_framework.urlpatterns import format_suffix_patterns
# Manual URL patterns with format suffixes
urlpatterns = [
path('books/', BookListView.as_view(), name='book-list'),
path('books/<int:pk>/', BookDetailView.as_view(), name='book-detail'),
]
# Add format suffix support
urlpatterns = format_suffix_patterns(urlpatterns)
# Now supports:
# /books/ or /books.json or /books.xml
# /books/1/ or /books/1.json or /books/1.xml
# Custom allowed formats
urlpatterns = format_suffix_patterns(
urlpatterns,
allowed=['json', 'xml', 'yaml']
)
# Required suffix
urlpatterns = format_suffix_patterns(
urlpatterns,
suffix_required=True # Must include format suffix
)# Simple router without API root
simple_router = SimpleRouter()
simple_router.register(r'books', BookViewSet)
# Default router without root view
no_root_router = DefaultRouter()
no_root_router.include_root_view = False
no_root_router.register(r'books', BookViewSet)
# Custom trailing slash behavior
no_slash_router = DefaultRouter(trailing_slash=False)
no_slash_router.register(r'books', BookViewSet)
# URLs without trailing slashes:
# /api/books (instead of /api/books/)
# /api/books/1 (instead of /api/books/1/)# Print all generated URLs for debugging
router = DefaultRouter()
router.register(r'books', BookViewSet)
router.register(r'authors', AuthorViewSet)
# Get all URL patterns
url_patterns = router.urls
for pattern in url_patterns:
print(f"Pattern: {pattern.pattern}")
print(f"Name: {pattern.name}")
print(f"Callback: {pattern.callback}")
print("---")def escape_curly_brackets(url_path):
"""
Escape curly brackets in URL path for regex patterns.
Args:
url_path (str): URL path that may contain curly brackets
Returns:
str: Escaped URL path
"""
def flatten(list_of_lists):
"""
Flatten nested lists into single list.
Args:
list_of_lists (list): Nested list structure
Returns:
list: Flattened list
"""
def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
"""
Add format suffix patterns to URL patterns.
Args:
urlpatterns (list): List of URL patterns
suffix_required (bool): Whether format suffix is required
allowed (list): List of allowed format suffixes
Returns:
list: URL patterns with format suffix support
"""
def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route=None):
"""
Apply suffix patterns to URL pattern list recursively.
Internal function used by format_suffix_patterns.
Args:
urlpatterns (list): URL patterns to process
suffix_pattern (str): Suffix pattern regex
suffix_required (bool): Whether suffix is required
suffix_route (str): Optional route suffix
Returns:
list: Processed URL patterns
"""Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework