Web APIs for Django, made easy.
—
Enhanced request and response objects providing consistent data access and content negotiation across different formats in Django REST Framework.
Enhanced request object wrapping Django's HttpRequest with additional API functionality.
class Request:
"""
Enhanced request object with additional API features.
"""
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
"""
Initialize DRF Request object.
Args:
request: Django HttpRequest object
parsers (list): List of parser instances
authenticators (list): List of authenticator instances
negotiator: Content negotiation instance
parser_context (dict): Additional context for parsers
"""
@property
def data(self):
"""
Parsed request body data.
Returns:
dict or list: Parsed data from request body
"""
@property
def query_params(self):
"""
Query parameters from URL (more semantic than request.GET).
Returns:
QueryDict: URL query parameters
"""
@property
def user(self):
"""
Authenticated user for this request.
Returns:
User instance or AnonymousUser
"""
@property
def auth(self):
"""
Authentication credentials for this request.
Returns:
Authentication token/credentials or None
"""
@property
def successful_authenticator(self):
"""
Authenticator instance that successfully authenticated the request.
Returns:
Authentication instance or None
"""
@property
def accepted_renderer(self):
"""
Renderer selected by content negotiation.
Returns:
Renderer instance
"""
@property
def accepted_media_type(self):
"""
Media type selected by content negotiation.
Returns:
str: Media type string
"""
@property
def version(self):
"""
API version for this request.
Returns:
str: Version string or None
"""
@property
def versioning_scheme(self):
"""
Versioning scheme instance for this request.
Returns:
Versioning scheme instance or None
"""Enhanced response object for API responses with content negotiation support.
class Response(SimpleTemplateResponse):
"""
Enhanced response object for API responses.
"""
def __init__(self, data=None, status=None, template_name=None,
headers=None, exception=False, content_type=None):
"""
Initialize API response.
Args:
data: Response data (will be serialized)
status (int): HTTP status code
template_name (str): Template for HTML rendering
headers (dict): Additional response headers
exception (bool): Whether response is for exception
content_type (str): Response content type
"""
@property
def data(self):
"""
Response data to be serialized.
Returns:
Response data
"""
@data.setter
def data(self, value):
"""
Set response data.
Args:
value: Data to serialize in response
"""
@property
def status_code(self):
"""
HTTP status code for response.
Returns:
int: HTTP status code
"""
@status_code.setter
def status_code(self, value):
"""
Set HTTP status code.
Args:
value (int): HTTP status code
"""
@property
def status_text(self):
"""
HTTP status text for response.
Returns:
str: HTTP status text
"""
@property
def rendered_content(self):
"""
Rendered response content.
Returns:
bytes: Rendered content
"""Utility classes and functions for request handling.
class ForcedAuthentication:
"""
Force specific user authentication for testing.
"""
def __init__(self, user, token=None):
"""
Args:
user: User instance to authenticate as
token: Optional authentication token
"""
self.user = user
self.token = token
class Empty:
"""
Placeholder for empty request data.
"""
pass
def is_form_media_type(media_type):
"""
Check if media type represents form data.
Args:
media_type (str): Media type to check
Returns:
bool: True if form media type
"""
def clone_request(request, method):
"""
Clone request with different HTTP method.
Args:
request: Original request object
method (str): New HTTP method
Returns:
Request: Cloned request with new method
"""
class override_method:
"""
Context manager for temporarily overriding request method.
"""
def __init__(self, request, method):
"""
Args:
request: Request object to modify
method (str): Temporary method to use
"""
self.request = request
self.method = method
self.original_method = None
def __enter__(self):
"""Enter context with overridden method."""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Restore original method."""from rest_framework.views import APIView
from rest_framework.response import Response
class BookView(APIView):
def post(self, request):
# Access parsed request data (JSON, form data, etc.)
title = request.data.get('title')
author = request.data.get('author')
# Access query parameters
format_type = request.query_params.get('format', 'json')
# Access authenticated user
user = request.user
# Access authentication token/credentials
auth_token = request.auth
# Create response
response_data = {
'message': f'Book "{title}" by {author} created',
'user': user.username if user.is_authenticated else 'anonymous',
'format': format_type
}
return Response(response_data, status=201)from rest_framework.response import Response
from rest_framework import status
class BookAPIView(APIView):
def get(self, request):
# Simple data response
data = {'books': ['Book 1', 'Book 2']}
return Response(data)
def post(self, request):
# Response with custom status
return Response(
{'message': 'Book created'},
status=status.HTTP_201_CREATED
)
def put(self, request):
# Response with custom headers
headers = {'X-Custom-Header': 'custom-value'}
return Response(
{'message': 'Book updated'},
headers=headers
)
def delete(self, request):
# Empty response with status code
return Response(status=status.HTTP_204_NO_CONTENT)class BookView(APIView):
def get(self, request):
books = Book.objects.all()
# Check accepted media type
if request.accepted_media_type == 'application/json':
# JSON response
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
elif request.accepted_media_type == 'text/html':
# HTML response
return Response(
{'books': books},
template_name='books/list.html'
)
else:
# Default response
return Response({'error': 'Unsupported media type'})API versioning allows maintaining backward compatibility while evolving APIs. Multiple versioning strategies are supported.
class BaseVersioning:
"""
Base class for all versioning schemes.
"""
default_version = None
allowed_versions = None
version_param = 'version'
def determine_version(self, request, *args, **kwargs):
"""
Determine API version from request.
Must be implemented by subclasses.
Returns:
str: Version string
"""
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
"""
Reverse URL with version information.
"""
class AcceptHeaderVersioning(BaseVersioning):
"""
Version determined from Accept header media type parameters.
Example: Accept: application/json; version=1.0
"""
class URLPathVersioning(BaseVersioning):
"""
Version determined from URL path keyword arguments.
Example: /v1/books/
"""
class NamespaceVersioning(BaseVersioning):
"""
Version determined from URL namespace.
Example: URL patterns with version namespaces
"""
class HostNameVersioning(BaseVersioning):
"""
Version determined from request hostname.
Example: v1.api.example.com
"""
class QueryParameterVersioning(BaseVersioning):
"""
Version determined from query parameters.
Example: /books/?version=1.0
"""Configure versioning in Django settings:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
'DEFAULT_VERSION': '1.0',
'ALLOWED_VERSIONS': ['1.0', '1.1', '2.0'],
'VERSION_PARAM': 'version',
}# urls.py
from django.urls import path, include
urlpatterns = [
path('v1/', include('myapp.urls', namespace='v1')),
path('v2/', include('myapp.urls', namespace='v2')),
]
# Using in views
class BookViewSet(ModelViewSet):
def list(self, request):
version = request.version
if version == '1.0':
# Version 1.0 response format
serializer = BookSerializerV1(books, many=True)
elif version == '2.0':
# Version 2.0 response format
serializer = BookSerializerV2(books, many=True)
return Response(serializer.data)# Client request:
# Accept: application/json; version=1.1
class BookAPIView(APIView):
def get(self, request):
version = request.version # '1.1'
books = Book.objects.all()
if version == '1.0':
data = [{'id': b.id, 'title': b.title} for b in books]
else: # version >= 1.1
data = [{'id': b.id, 'title': b.title, 'author': b.author} for b in books]
return Response({'books': data, 'version': version})from rest_framework.versioning import BaseVersioning
from rest_framework import exceptions
class CustomHeaderVersioning(BaseVersioning):
"""
Custom versioning based on X-API-Version header.
"""
def determine_version(self, request, *args, **kwargs):
version = request.META.get('HTTP_X_API_VERSION', self.default_version)
if not self.is_allowed_version(version):
raise exceptions.NotAcceptable(
'Invalid API version. Supported versions: {}'.format(
', '.join(self.allowed_versions)
)
)
return versionclass FileUploadView(APIView):
def post(self, request):
# Access uploaded files
uploaded_file = request.data.get('file')
if uploaded_file:
# Process the file
file_content = uploaded_file.read()
file_name = uploaded_file.name
file_size = uploaded_file.size
return Response({
'message': 'File uploaded successfully',
'filename': file_name,
'size': file_size
})
return Response(
{'error': 'No file provided'},
status=status.HTTP_400_BAD_REQUEST
)from rest_framework.request import override_method
class CustomView(APIView):
def post(self, request):
# Temporarily override method for internal processing
with override_method(request, 'PUT'):
# Process as if it were a PUT request
return self.handle_update(request)
def handle_update(self, request):
# Method sees request.method as 'PUT'
return Response({'method': request.method})from rest_framework.request import ForcedAuthentication
from rest_framework.test import APIRequestFactory
# In tests
factory = APIRequestFactory()
user = User.objects.create_user('testuser')
# Create request with forced authentication
request = factory.post('/api/books/', {'title': 'Test Book'})
force_auth = ForcedAuthentication(user)
# Apply authentication
request.user = user
request.auth = NoneInstall with Tessl CLI
npx tessl i tessl/pypi-djangorestframework