Web APIs for Django, made easy.
—
Parser and renderer classes for handling multiple content types with automatic format detection and conversion in Django REST Framework.
Parser classes handle incoming request data in different formats.
class BaseParser:
"""
Base class for all parser implementations.
"""
media_type = None # Media type this parser handles
def parse(self, stream, media_type=None, parser_context=None):
"""
Parse incoming data stream.
Args:
stream: Input data stream
media_type (str): Request content type
parser_context (dict): Additional context
Returns:
Parsed data
Raises:
ParseError: If parsing fails
"""
raise NotImplementedError
class JSONParser(BaseParser):
"""
Parser for JSON data.
"""
media_type = 'application/json'
def parse(self, stream, media_type=None, parser_context=None):
"""
Parse JSON data from stream.
Returns:
dict or list: Parsed JSON data
Raises:
ParseError: If JSON is malformed
"""
class FormParser(BaseParser):
"""
Parser for HTML form data (application/x-www-form-urlencoded).
"""
media_type = 'application/x-www-form-urlencoded'
def parse(self, stream, media_type=None, parser_context=None):
"""
Parse form-encoded data.
Returns:
QueryDict: Parsed form data
"""
class MultiPartParser(BaseParser):
"""
Parser for multipart form data (multipart/form-data).
"""
media_type = 'multipart/form-data'
def parse(self, stream, media_type=None, parser_context=None):
"""
Parse multipart form data including file uploads.
Returns:
DataAndFiles: Container with .data and .files attributes
"""
class FileUploadParser(BaseParser):
"""
Parser for raw file uploads.
"""
media_type = '*/*' # Accepts any media type
errors = {
'unhandled': 'FileUploadParser can only handle a single file upload.',
'no_filename': 'Missing filename. Request should include Content-Disposition header with filename parameter.',
}
def parse(self, stream, media_type=None, parser_context=None):
"""
Parse raw file upload.
Returns:
dict: Dictionary with single file entry
Raises:
ParseError: If multiple files or no filename
"""Renderer classes serialize data into different output formats.
class BaseRenderer:
"""
Base class for all renderer implementations.
"""
media_type = None # Media type this renderer produces
format = None # Format name for URL suffixes
charset = 'utf-8' # Character encoding
render_style = 'text' # Rendering style hint
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render data into output format.
Args:
data: Data to render
accepted_media_type (str): Accepted media type from negotiation
renderer_context (dict): Additional context
Returns:
bytes: Rendered output
"""
raise NotImplementedError
class JSONRenderer(BaseRenderer):
"""
Renderer for JSON output.
"""
media_type = 'application/json'
format = 'json'
encoder_class = encoders.JSONEncoder
ensure_ascii = True
compact = True
strict = True
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render data as JSON.
Returns:
bytes: JSON-encoded data
"""
class TemplateHTMLRenderer(BaseRenderer):
"""
Renderer for HTML templates.
"""
media_type = 'text/html'
format = 'html'
template_name = None
exception_template_names = []
charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render data using Django template.
Returns:
bytes: Rendered HTML
"""
class StaticHTMLRenderer(TemplateHTMLRenderer):
"""
Renderer for static HTML content.
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render static HTML string.
Returns:
bytes: HTML content as bytes
"""
class BrowsableAPIRenderer(BaseRenderer):
"""
Renderer for browsable API interface.
"""
media_type = 'text/html'
format = 'api'
template = 'rest_framework/api.html'
filter_template = 'rest_framework/filters/base.html'
code_style = 'emacs'
charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render browsable API interface.
Returns:
bytes: HTML for browsable API
"""
class HTMLFormRenderer(BaseRenderer):
"""
Renderer for HTML forms.
"""
media_type = 'application/x-www-form-urlencoded'
format = 'form'
template_pack = 'rest_framework/vertical/'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render HTML form for data input.
Returns:
str: HTML form markup
"""
class MultiPartRenderer(BaseRenderer):
"""
Renderer for multipart form data.
"""
media_type = 'multipart/form-data'
format = 'multipart'
BOUNDARY = 'BoUnDaRyStRiNg'
charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render multipart form data.
Returns:
bytes: Multipart-encoded data
"""
class OpenAPIRenderer(BaseRenderer):
"""
Renderer for OpenAPI schema format.
"""
media_type = 'application/vnd.oai.openapi'
format = 'openapi'
charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render OpenAPI schema.
Returns:
bytes: YAML-formatted OpenAPI schema
"""
class JSONOpenAPIRenderer(BaseRenderer):
"""
Renderer for OpenAPI schema in JSON format.
"""
media_type = 'application/vnd.oai.openapi+json'
format = 'openapi-json'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render OpenAPI schema as JSON.
Returns:
bytes: JSON-formatted OpenAPI schema
"""Classes that determine appropriate parser/renderer based on request.
class BaseContentNegotiation:
"""
Base class for content negotiation.
"""
def select_parser(self, request, parsers):
"""
Select appropriate parser for request.
Args:
request: HTTP request
parsers (list): Available parser instances
Returns:
Parser: Selected parser instance
Raises:
UnsupportedMediaType: If no suitable parser found
"""
raise NotImplementedError
def select_renderer(self, request, renderers, format_suffix=None):
"""
Select appropriate renderer for response.
Args:
request: HTTP request
renderers (list): Available renderer instances
format_suffix (str): URL format suffix
Returns:
tuple: (renderer, media_type)
Raises:
NotAcceptable: If no acceptable renderer found
"""
raise NotImplementedError
class DefaultContentNegotiation(BaseContentNegotiation):
"""
Default content negotiation implementation.
"""
settings = api_settings
def select_parser(self, request, parsers):
"""
Select parser based on Content-Type header.
"""
def select_renderer(self, request, renderers, format_suffix=None):
"""
Select renderer based on Accept header and format suffix.
"""
def get_accept_list(self, request):
"""
Parse Accept header into list of media types with priorities.
Args:
request: HTTP request
Returns:
list: Sorted list of (media_type, priority) tuples
"""
def filter_renderers(self, renderers, format):
"""
Filter renderers by format suffix.
Args:
renderers (list): Available renderers
format (str): Format suffix
Returns:
list: Filtered renderers
"""Helper classes for content negotiation.
class DataAndFiles:
"""
Container for parsed multipart data.
"""
def __init__(self, data, files):
"""
Args:
data (dict): Form field data
files (dict): Uploaded file data
"""
self.data = data
self.files = filesfrom rest_framework.parsers import BaseParser
from rest_framework.exceptions import ParseError
import yaml
class YAMLParser(BaseParser):
"""
Parser for YAML data.
"""
media_type = 'application/yaml'
def parse(self, stream, media_type=None, parser_context=None):
"""
Parse YAML data from stream.
"""
try:
data = stream.read()
return yaml.safe_load(data)
except yaml.YAMLError as exc:
raise ParseError(f'YAML parse error: {exc}')
# Usage in view
from rest_framework.views import APIView
class YAMLView(APIView):
parser_classes = [YAMLParser]
def post(self, request):
# request.data contains parsed YAML
return Response({'received': request.data})from rest_framework.renderers import BaseRenderer
import yaml
class YAMLRenderer(BaseRenderer):
"""
Renderer for YAML output.
"""
media_type = 'application/yaml'
format = 'yaml'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render data as YAML.
"""
return yaml.dump(data, default_flow_style=False).encode('utf-8')
# Usage in view
class YAMLView(APIView):
renderer_classes = [YAMLRenderer]
def get(self, request):
data = {'message': 'Hello World'}
return Response(data)from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
class BookView(APIView):
"""
View supporting multiple input and output formats.
"""
parser_classes = [JSONParser, FormParser, MultiPartParser]
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
def get(self, request):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
# Response format determined by Accept header or format suffix
return Response(serializer.data)
def post(self, request):
# Input format determined by Content-Type header
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)# settings.py
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
'myapp.parsers.YAMLParser', # Custom parser
],
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'myapp.renderers.YAMLRenderer', # Custom renderer
],
}from rest_framework.parsers import FileUploadParser
from rest_framework.response import Response
from rest_framework import status
class FileUploadView(APIView):
parser_classes = [FileUploadParser]
def put(self, request, filename):
"""
Handle raw file upload.
"""
file_obj = request.data['file']
# Process the uploaded file
with open(f'/uploads/{filename}', 'wb') as destination:
for chunk in file_obj.chunks():
destination.write(chunk)
return Response(
{'message': f'File {filename} uploaded successfully'},
status=status.HTTP_201_CREATED
)
# Client usage:
# PUT /api/upload/document.pdf
# Content-Type: application/pdf
# Content-Disposition: attachment; filename="document.pdf"
# <binary file data>class BookListView(APIView):
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
def get(self, request):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
# For HTML requests, provide template context
return Response({
'books': serializer.data,
'title': 'Book List'
}, template_name='books/list.html')
# books/list.html template:
# <h1>{{ title }}</h1>
# {% for book in books %}
# <div>{{ book.title }} by {{ book.author }}</div>
# {% endfor %}class BookView(APIView):
def get(self, request):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
# Check what format was requested
if request.accepted_renderer.format == 'json':
# JSON response
return Response(serializer.data)
elif request.accepted_renderer.format == 'html':
# HTML response
return Response(
{'books': serializer.data},
template_name='books/list.html'
)
else:
# Default response
return Response(serializer.data)
# Client requests:
# Accept: application/json -> JSON response
# Accept: text/html -> HTML response
# Accept: */* -> Default formatfrom rest_framework.content_negotiation import BaseContentNegotiation
class IgnoreClientContentNegotiation(BaseContentNegotiation):
"""
Content negotiation that ignores client preferences.
"""
def select_parser(self, request, parsers):
"""
Always select the first parser.
"""
return parsers[0]
def select_renderer(self, request, renderers, format_suffix=None):
"""
Always select JSON renderer if available.
"""
for renderer in renderers:
if renderer.format == 'json':
return (renderer, renderer.media_type)
# Fallback to first renderer
return (renderers[0], renderers[0].media_type)
# Usage in view
class CustomNegotiationView(APIView):
content_negotiation_class = IgnoreClientContentNegotiation
def get(self, request):
return Response({'message': 'Always JSON'})from rest_framework.views import APIView
from rest_framework.exceptions import ParseError
class StrictJSONView(APIView):
parser_classes = [JSONParser]
def post(self, request):
try:
# request.data automatically parsed by JSONParser
data = request.data
if not isinstance(data, dict):
raise ParseError("Expected JSON object")
return Response({'received': data})
except ParseError as e:
return Response(
{'error': str(e)},
status=status.HTTP_400_BAD_REQUEST
)def zero_as_none(value):
"""
Convert zero values to None for rendering.
Args:
value: Value to check
Returns:
None if value is zero, otherwise original value
"""Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework